Merge pull request #2973 from HeatXD/netplay_dev
Upstream Netplay Changes
This commit is contained in:
commit
0c3f755b80
|
@ -196,8 +196,8 @@ typedef struct {
|
||||||
} chat;
|
} chat;
|
||||||
struct {
|
struct {
|
||||||
int nFrameOfDesync;
|
int nFrameOfDesync;
|
||||||
uint16_t ourCheckSum;
|
uint32_t ourCheckSum;
|
||||||
uint16_t remoteChecksum;
|
uint32_t remoteChecksum;
|
||||||
} desync;
|
} desync;
|
||||||
} u;
|
} u;
|
||||||
} GGPOEvent;
|
} GGPOEvent;
|
||||||
|
@ -248,7 +248,7 @@ typedef struct {
|
||||||
* free_buffer - Frees a game state allocated in save_game_state. You
|
* free_buffer - Frees a game state allocated in save_game_state. You
|
||||||
* should deallocate the memory contained in the buffer.
|
* should deallocate the memory contained in the buffer.
|
||||||
*/
|
*/
|
||||||
void (__cdecl *free_buffer)(void* context, void *buffer);
|
void (__cdecl *free_buffer)(void* context, void *buffer, int frame);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* advance_frame - Called during a rollback. You should advance your game
|
* advance_frame - Called during a rollback. You should advance your game
|
||||||
|
|
|
@ -430,19 +430,20 @@ GGPOErrorCode
|
||||||
Peer2PeerBackend::IncrementFrame(uint16_t checksum1)
|
Peer2PeerBackend::IncrementFrame(uint16_t checksum1)
|
||||||
{
|
{
|
||||||
auto currentFrame = _sync.GetFrameCount();
|
auto currentFrame = _sync.GetFrameCount();
|
||||||
|
_sync.IncrementFrame();
|
||||||
|
uint32 cSum = _sync.GetLastSavedFrame().checksum;
|
||||||
char buf[256];
|
char buf[256];
|
||||||
uint16_t cSum = checksum1;
|
Log("End of frame (%d)...\n", currentFrame);
|
||||||
Log("End of frame (%d)...\n", _sync.GetFrameCount());
|
|
||||||
static int maxDif = 0;
|
static int maxDif = 0;
|
||||||
if (_pendingCheckSums.count(_sync.GetFrameCount()))
|
if (_pendingCheckSums.count(currentFrame))
|
||||||
{
|
{
|
||||||
auto max = _pendingCheckSums.rbegin()->first;
|
auto max = _pendingCheckSums.rbegin()->first;
|
||||||
auto diff = max - currentFrame;
|
auto diff = max - currentFrame;
|
||||||
maxDif = max(maxDif, diff);
|
maxDif = max(maxDif, diff);
|
||||||
int oldChecksum = _pendingCheckSums[_sync.GetFrameCount()];
|
int oldChecksum = _pendingCheckSums[currentFrame];
|
||||||
_pendingCheckSums[_sync.GetFrameCount()] = cSum;
|
_pendingCheckSums[currentFrame] = cSum;
|
||||||
sprintf_s<256>(buf, "Replace local checksum for frame %d: %d with %d, newest frame is %d, max diff %d\n", _sync.GetFrameCount(), oldChecksum, _pendingCheckSums[_sync.GetFrameCount()], max, maxDif);
|
sprintf_s<256>(buf, "Replace local checksum for frame %d: %d with %d, newest frame is %d, max diff %d\n",
|
||||||
|
currentFrame, oldChecksum, _pendingCheckSums[currentFrame], max, maxDif);
|
||||||
|
|
||||||
if (currentFrame <= _confirmedCheckSumFrame)
|
if (currentFrame <= _confirmedCheckSumFrame)
|
||||||
{
|
{
|
||||||
|
@ -461,16 +462,13 @@ Peer2PeerBackend::IncrementFrame(uint16_t checksum1)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sprintf_s<256>(buf, "Added local checksum for frame %d: %d\n", _sync.GetFrameCount(), cSum);
|
sprintf_s<256>(buf, "Added local checksum for frame %d: %d\n", currentFrame, cSum);
|
||||||
//OutputDebugStringA(buf);
|
//OutputDebugStringA(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
_pendingCheckSums[_sync.GetFrameCount()]= cSum ;
|
_pendingCheckSums[currentFrame] = cSum;
|
||||||
|
|
||||||
|
DoPoll();
|
||||||
|
|
||||||
_sync.IncrementFrame();
|
|
||||||
DoPoll();
|
|
||||||
PollSyncEvents();
|
PollSyncEvents();
|
||||||
|
|
||||||
return GGPO_OK;
|
return GGPO_OK;
|
||||||
|
@ -511,7 +509,7 @@ Peer2PeerBackend::PollUdpProtocolEvents(void)
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Peer2PeerBackend::CheckRemoteChecksum(int framenumber, uint16 cs)
|
void Peer2PeerBackend::CheckRemoteChecksum(int framenumber, uint32 cs)
|
||||||
{
|
{
|
||||||
if (framenumber <= _sync.MaxPredictionFrames())
|
if (framenumber <= _sync.MaxPredictionFrames())
|
||||||
return;
|
return;
|
||||||
|
@ -522,7 +520,7 @@ void Peer2PeerBackend::CheckRemoteChecksum(int framenumber, uint16 cs)
|
||||||
|
|
||||||
int Peer2PeerBackend::HowFarBackForChecksums()const
|
int Peer2PeerBackend::HowFarBackForChecksums()const
|
||||||
{
|
{
|
||||||
return 16;
|
return _sync.MaxPredictionFrames() + 2;
|
||||||
}/*
|
}/*
|
||||||
uint16 Peer2PeerBackend::GetChecksumForConfirmedFrame(int frameNumber) const
|
uint16 Peer2PeerBackend::GetChecksumForConfirmedFrame(int frameNumber) const
|
||||||
{
|
{
|
||||||
|
|
|
@ -82,11 +82,11 @@ protected:
|
||||||
int nFrame;
|
int nFrame;
|
||||||
int checkSum;;
|
int checkSum;;
|
||||||
};
|
};
|
||||||
std::map<int, uint16> _pendingCheckSums;
|
std::map<int, uint32> _pendingCheckSums;
|
||||||
std::map<int, uint16> _confirmedCheckSums;
|
std::map<int, uint32> _confirmedCheckSums;
|
||||||
|
|
||||||
// uint16 GetChecksumForConfirmedFrame(int frameNumber) const;
|
// uint16 GetChecksumForConfirmedFrame(int frameNumber) const;
|
||||||
void CheckRemoteChecksum(int framenumber, uint16 cs);
|
void CheckRemoteChecksum(int framenumber, uint32 cs);
|
||||||
int HowFarBackForChecksums()const;
|
int HowFarBackForChecksums()const;
|
||||||
int _confirmedCheckSumFrame = -500;
|
int _confirmedCheckSumFrame = -500;
|
||||||
void CheckDesync();
|
void CheckDesync();
|
||||||
|
|
|
@ -158,7 +158,7 @@ SyncTestBackend::IncrementFrame(uint16_t cs)
|
||||||
RaiseSyncError("Checksum for frame %d does not match saved (%d != %d)", frame, checksum, info.checksum);
|
RaiseSyncError("Checksum for frame %d does not match saved (%d != %d)", frame, checksum, info.checksum);
|
||||||
}
|
}
|
||||||
printf("Checksum %08d for frame %d matches.\n", checksum, info.frame);
|
printf("Checksum %08d for frame %d matches.\n", checksum, info.frame);
|
||||||
_callbacks.free_buffer(_callbacks.context, info.buf);
|
_callbacks.free_buffer(_callbacks.context, info.buf, info.frame);
|
||||||
}
|
}
|
||||||
_last_verified = frame;
|
_last_verified = frame;
|
||||||
_rollingback = false;
|
_rollingback = false;
|
||||||
|
|
|
@ -24,7 +24,7 @@ struct GameInput {
|
||||||
int frame;
|
int frame;
|
||||||
int size; /* size in bytes of the entire input for all players */
|
int size; /* size in bytes of the entire input for all players */
|
||||||
char bits[GAMEINPUT_MAX_BYTES * GAMEINPUT_MAX_PLAYERS];
|
char bits[GAMEINPUT_MAX_BYTES * GAMEINPUT_MAX_PLAYERS];
|
||||||
uint16 checksum;
|
uint32 checksum;
|
||||||
bool is_null() { return frame == NullFrame; }
|
bool is_null() { return frame == NullFrame; }
|
||||||
void init(int frame, char *bits, int size, int offset);
|
void init(int frame, char *bits, int size, int offset);
|
||||||
void init(int frame, char *bits, int size);
|
void init(int frame, char *bits, int size);
|
||||||
|
|
|
@ -67,7 +67,7 @@ struct UdpMsg
|
||||||
int ack_frame:31;
|
int ack_frame:31;
|
||||||
|
|
||||||
uint16 num_bits;
|
uint16 num_bits;
|
||||||
uint16 checksum16;
|
uint32 checksum32;
|
||||||
uint8 input_size; // XXX: shouldn't be in every single packet!
|
uint8 input_size; // XXX: shouldn't be in every single packet!
|
||||||
uint8 bits[MAX_COMPRESSED_BITS]; /* must be last */
|
uint8 bits[MAX_COMPRESSED_BITS]; /* must be last */
|
||||||
} input;
|
} input;
|
||||||
|
|
|
@ -134,7 +134,7 @@ UdpProtocol::SendPendingOutput()
|
||||||
ASSERT(last.frame == -1 || last.frame + 1 == msg->u.input.start_frame);
|
ASSERT(last.frame == -1 || last.frame + 1 == msg->u.input.start_frame);
|
||||||
for (j = 0; j < _pending_output.size(); j++) {
|
for (j = 0; j < _pending_output.size(); j++) {
|
||||||
GameInput ¤t = _pending_output.item(j);
|
GameInput ¤t = _pending_output.item(j);
|
||||||
msg->u.input.checksum16 = current.checksum;
|
msg->u.input.checksum32 = current.checksum;
|
||||||
if (memcmp(current.bits, last.bits, current.size) != 0) {
|
if (memcmp(current.bits, last.bits, current.size) != 0) {
|
||||||
ASSERT((GAMEINPUT_MAX_BYTES * GAMEINPUT_MAX_PLAYERS * 8) < (1 << BITVECTOR_NIBBLE_SIZE));
|
ASSERT((GAMEINPUT_MAX_BYTES * GAMEINPUT_MAX_PLAYERS * 8) < (1 << BITVECTOR_NIBBLE_SIZE));
|
||||||
for (i = 0; i < current.size * 8; i++) {
|
for (i = 0; i < current.size * 8; i++) {
|
||||||
|
@ -627,7 +627,7 @@ UdpProtocol::OnInput(UdpMsg *msg, int len)
|
||||||
char desc[1024];
|
char desc[1024];
|
||||||
ASSERT(currentFrame == _last_received_input.frame + 1);
|
ASSERT(currentFrame == _last_received_input.frame + 1);
|
||||||
_last_received_input.frame = currentFrame;
|
_last_received_input.frame = currentFrame;
|
||||||
_last_received_input.checksum = msg->u.input.checksum16;
|
_last_received_input.checksum = msg->u.input.checksum32;
|
||||||
/*
|
/*
|
||||||
* Send the event to the emualtor
|
* Send the event to the emualtor
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -94,8 +94,8 @@ public:
|
||||||
void ApplyToEvents(std::function<void(UdpProtocol::Event&)> cb);
|
void ApplyToEvents(std::function<void(UdpProtocol::Event&)> cb);
|
||||||
void StartPollLoop();
|
void StartPollLoop();
|
||||||
void EndPollLoop();
|
void EndPollLoop();
|
||||||
std::map<int, uint16> _remoteCheckSums;
|
std::map<int, uint32> _remoteCheckSums;
|
||||||
std::map<int, uint16> _remoteCheckSumsThisFrame;
|
std::map<int, uint32> _remoteCheckSumsThisFrame;
|
||||||
protected:
|
protected:
|
||||||
enum State {
|
enum State {
|
||||||
Syncing,
|
Syncing,
|
||||||
|
|
|
@ -24,7 +24,7 @@ Sync::~Sync()
|
||||||
* structure so we can efficently copy frames via weak references.
|
* structure so we can efficently copy frames via weak references.
|
||||||
*/
|
*/
|
||||||
for (int i = 0; i < _savedstate.frames.size(); i++) {
|
for (int i = 0; i < _savedstate.frames.size(); i++) {
|
||||||
_callbacks.free_buffer(_callbacks.context, _savedstate.frames[i].buf);
|
_callbacks.free_buffer(_callbacks.context, _savedstate.frames[i].buf, _savedstate.frames[i].frame);
|
||||||
}
|
}
|
||||||
delete [] _input_queues;
|
delete [] _input_queues;
|
||||||
_input_queues = NULL;
|
_input_queues = NULL;
|
||||||
|
@ -204,7 +204,7 @@ Sync::SaveCurrentFrame()
|
||||||
*/
|
*/
|
||||||
SavedFrame *state = &_savedstate.frames[_savedstate.head];
|
SavedFrame *state = &_savedstate.frames[_savedstate.head];
|
||||||
if (state->buf) {
|
if (state->buf) {
|
||||||
_callbacks.free_buffer(_callbacks.context, state->buf);
|
_callbacks.free_buffer(_callbacks.context, state->buf, state->frame);
|
||||||
state->buf = NULL;
|
state->buf = NULL;
|
||||||
}
|
}
|
||||||
state->frame = _framecount;
|
state->frame = _framecount;
|
||||||
|
|
|
@ -61,7 +61,8 @@ public:
|
||||||
bool GetEvent(Event& e);
|
bool GetEvent(Event& e);
|
||||||
int MaxPredictionFrames() const { return _max_prediction_frames; }
|
int MaxPredictionFrames() const { return _max_prediction_frames; }
|
||||||
protected:
|
protected:
|
||||||
friend SyncTestBackend;
|
friend class SyncTestBackend;
|
||||||
|
friend class Peer2PeerBackend;
|
||||||
|
|
||||||
struct SavedFrame {
|
struct SavedFrame {
|
||||||
byte* buf;
|
byte* buf;
|
||||||
|
|
|
@ -7,12 +7,14 @@
|
||||||
#include "common/timer.h"
|
#include "common/timer.h"
|
||||||
#include "digital_controller.h"
|
#include "digital_controller.h"
|
||||||
#include "ggponet.h"
|
#include "ggponet.h"
|
||||||
|
#include "host.h"
|
||||||
#include "host_settings.h"
|
#include "host_settings.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>
|
#include <deque>
|
||||||
|
#include <xxhash.h>
|
||||||
Log_SetChannel(Netplay);
|
Log_SetChannel(Netplay);
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
@ -32,7 +34,7 @@ static bool NpAdvFrameCb(void* ctx, int flags);
|
||||||
static bool NpSaveFrameCb(void* ctx, unsigned char** buffer, int* len, int* checksum, int frame);
|
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 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 bool NpBeginGameCb(void* ctx, const char* game_name);
|
||||||
static void NpFreeBuffCb(void* ctx, void* buffer);
|
static void NpFreeBuffCb(void* ctx, void* buffer, int frame);
|
||||||
static bool NpOnEventCb(void* ctx, GGPOEvent* ev);
|
static bool NpOnEventCb(void* ctx, GGPOEvent* ev);
|
||||||
|
|
||||||
static Input ReadLocalInput();
|
static Input ReadLocalInput();
|
||||||
|
@ -45,7 +47,7 @@ static void SetSettings();
|
||||||
// l = local, r = remote
|
// l = local, r = remote
|
||||||
static s32 Start(s32 lhandle, u16 lport, std::string& raddr, u16 rport, s32 ldelay, u32 pred);
|
static s32 Start(s32 lhandle, u16 lport, std::string& raddr, u16 rport, s32 ldelay, u32 pred);
|
||||||
|
|
||||||
static void AdvanceFrame(u16 checksum = 0);
|
static void AdvanceFrame();
|
||||||
static void RunFrame();
|
static void RunFrame();
|
||||||
|
|
||||||
static s32 CurrentFrame();
|
static s32 CurrentFrame();
|
||||||
|
@ -57,6 +59,9 @@ static void InitializeFramePacing();
|
||||||
static void HandleTimeSyncEvent(float frame_delta, int update_interval);
|
static void HandleTimeSyncEvent(float frame_delta, int update_interval);
|
||||||
static void Throttle();
|
static void Throttle();
|
||||||
|
|
||||||
|
// Desync Detection
|
||||||
|
static void GenerateChecksumForFrame(int* checksum, int frame, unsigned char* buffer, int buffer_size);
|
||||||
|
static void GenerateDesyncReport(s32 desync_frame);
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
// Variables
|
// Variables
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -107,7 +112,7 @@ s32 Netplay::Start(s32 lhandle, u16 lport, std::string& raddr, u16 rport, s32 ld
|
||||||
ggpo_start_session(&s_ggpo, &cb, "Duckstation-Netplay", 2, sizeof(Netplay::Input), lport, MAX_ROLLBACK_FRAMES);
|
ggpo_start_session(&s_ggpo, &cb, "Duckstation-Netplay", 2, sizeof(Netplay::Input), lport, MAX_ROLLBACK_FRAMES);
|
||||||
// result = ggpo_start_synctest(&s_ggpo, &cb, (char*)"asdf", 2, sizeof(Netplay::Input), 1);
|
// result = ggpo_start_synctest(&s_ggpo, &cb, (char*)"asdf", 2, sizeof(Netplay::Input), 1);
|
||||||
|
|
||||||
ggpo_set_disconnect_timeout(s_ggpo, 3000);
|
ggpo_set_disconnect_timeout(s_ggpo, 2000);
|
||||||
ggpo_set_disconnect_notify_start(s_ggpo, 1000);
|
ggpo_set_disconnect_notify_start(s_ggpo, 1000);
|
||||||
|
|
||||||
for (int i = 1; i <= 2; i++)
|
for (int i = 1; i <= 2; i++)
|
||||||
|
@ -176,6 +181,11 @@ void Netplay::SetSettings()
|
||||||
si.SetIntValue("Main", "RunaheadFrameCount", 0);
|
si.SetIntValue("Main", "RunaheadFrameCount", 0);
|
||||||
si.SetBoolValue("Main", "RewindEnable", false);
|
si.SetBoolValue("Main", "RewindEnable", false);
|
||||||
|
|
||||||
|
// no block linking, it degrades savestate loading performance
|
||||||
|
si.SetBoolValue("CPU", "RecompilerBlockLinking", false);
|
||||||
|
// not sure its needed but enabled for now... TODO
|
||||||
|
si.SetBoolValue("GPU", "UseSoftwareRendererForReadbacks", true);
|
||||||
|
|
||||||
Host::Internal::SetNetplaySettingsLayer(&si);
|
Host::Internal::SetNetplaySettingsLayer(&si);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,19 +210,16 @@ void Netplay::UpdateThrottlePeriod()
|
||||||
|
|
||||||
void Netplay::HandleTimeSyncEvent(float frame_delta, int update_interval)
|
void Netplay::HandleTimeSyncEvent(float frame_delta, int update_interval)
|
||||||
{
|
{
|
||||||
// threshold to what is is with correcting for.
|
// Distribute the frame difference over the next N * 0.75 frames.
|
||||||
if (frame_delta <= 1.0f)
|
|
||||||
return;
|
|
||||||
|
|
||||||
float total_time = frame_delta * s_frame_period;
|
|
||||||
// Distribute the frame difference over the next N * 0.8 frames.
|
|
||||||
// only part of the interval time is used since we want to come back to normal speed.
|
// only part of the interval time is used since we want to come back to normal speed.
|
||||||
// otherwise we will keep spiraling into unplayable gameplay.
|
// otherwise we will keep spiraling into unplayable gameplay.
|
||||||
float added_time_per_frame = -(total_time / (static_cast<float>(update_interval) * 0.8f));
|
float total_time = (frame_delta * s_frame_period) / 4;
|
||||||
|
float mun_timesync_frames = update_interval * 0.75f;
|
||||||
|
float added_time_per_frame = -(total_time / mun_timesync_frames);
|
||||||
float iterations_per_frame = 1.0f / s_frame_period;
|
float iterations_per_frame = 1.0f / s_frame_period;
|
||||||
|
|
||||||
s_target_speed = (s_frame_period + added_time_per_frame) * iterations_per_frame;
|
s_target_speed = (s_frame_period + added_time_per_frame) * iterations_per_frame;
|
||||||
s_next_timesync_recovery_frame = CurrentFrame() + static_cast<s32>(std::ceil(static_cast<float>(update_interval) * 0.8f));
|
s_next_timesync_recovery_frame = CurrentFrame() + static_cast<s32>(std::ceil(mun_timesync_frames));
|
||||||
|
|
||||||
UpdateThrottlePeriod();
|
UpdateThrottlePeriod();
|
||||||
|
|
||||||
|
@ -264,15 +271,57 @@ void Netplay::Throttle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Netplay::AdvanceFrame(u16 checksum)
|
void Netplay::GenerateChecksumForFrame(int* checksum, int frame, unsigned char* buffer, int buffer_size)
|
||||||
{
|
{
|
||||||
ggpo_advance_frame(s_ggpo, checksum);
|
const u32 sliding_window_size = 4096 * 4; // 4 pages.
|
||||||
|
const u32 num_group_of_pages = buffer_size / sliding_window_size;
|
||||||
|
const u32 start_position = (frame % num_group_of_pages) * sliding_window_size;
|
||||||
|
*checksum = XXH32(buffer + start_position, sliding_window_size, frame);
|
||||||
|
// Log_VerbosePrintf("Netplay Checksum: f:%d wf:%d c:%u", frame, frame % num_group_of_pages, *checksum);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Netplay::GenerateDesyncReport(s32 desync_frame)
|
||||||
|
{
|
||||||
|
std::string path = "\\netplaylogs\\desync_frame_" + std::to_string(desync_frame) + "_p" +
|
||||||
|
std::to_string(s_local_handle) + "_" + System::GetRunningSerial() + "_.txt";
|
||||||
|
std::string filename = EmuFolders::Dumps + path;
|
||||||
|
|
||||||
|
std::unique_ptr<ByteStream> stream =
|
||||||
|
ByteStream::OpenFile(filename.c_str(), BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_WRITE | BYTESTREAM_OPEN_TRUNCATE |
|
||||||
|
BYTESTREAM_OPEN_ATOMIC_UPDATE | BYTESTREAM_OPEN_STREAMED);
|
||||||
|
if (!stream)
|
||||||
|
{
|
||||||
|
Log_VerbosePrint("desync log creation failed to create stream");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ByteStream::WriteBinaryToStream(stream.get(),
|
||||||
|
s_save_buffer_pool.back().get()->state_stream.get()->GetMemoryPointer(),
|
||||||
|
s_save_buffer_pool.back().get()->state_stream.get()->GetMemorySize()))
|
||||||
|
{
|
||||||
|
Log_VerbosePrint("desync log creation failed to write the stream");
|
||||||
|
stream->Discard();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* stream->Write(s_save_buffer_pool.back().get()->state_stream.get()->GetMemoryPointer(),
|
||||||
|
s_save_buffer_pool.back().get()->state_stream.get()->GetMemorySize());*/
|
||||||
|
|
||||||
|
stream->Commit();
|
||||||
|
|
||||||
|
Log_VerbosePrintf("desync log created for frame %d", desync_frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Netplay::AdvanceFrame()
|
||||||
|
{
|
||||||
|
ggpo_advance_frame(s_ggpo, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Netplay::RunFrame()
|
void Netplay::RunFrame()
|
||||||
{
|
{
|
||||||
|
// housekeeping
|
||||||
|
ggpo_idle(s_ggpo);
|
||||||
// run game
|
// run game
|
||||||
bool need_idle = true;
|
|
||||||
auto result = GGPO_OK;
|
auto result = GGPO_OK;
|
||||||
int disconnect_flags = 0;
|
int disconnect_flags = 0;
|
||||||
Netplay::Input inputs[2] = {};
|
Netplay::Input inputs[2] = {};
|
||||||
|
@ -291,13 +340,8 @@ void Netplay::RunFrame()
|
||||||
// enable again when rolling back done
|
// enable again when rolling back done
|
||||||
SPU::SetAudioOutputMuted(false);
|
SPU::SetAudioOutputMuted(false);
|
||||||
NetplayAdvanceFrame(inputs, disconnect_flags);
|
NetplayAdvanceFrame(inputs, disconnect_flags);
|
||||||
// coming here means that the system doesnt need to idle anymore
|
|
||||||
need_idle = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// allow ggpo to do housekeeping if needed
|
|
||||||
if (need_idle)
|
|
||||||
ggpo_idle(s_ggpo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 Netplay::CurrentFrame()
|
s32 Netplay::CurrentFrame()
|
||||||
|
@ -372,9 +416,14 @@ void Netplay::StartNetplaySession(s32 local_handle, u16 local_port, std::string&
|
||||||
s_game_path = std::move(game_path);
|
s_game_path = std::move(game_path);
|
||||||
// create session
|
// create session
|
||||||
int result = Netplay::Start(local_handle, local_port, remote_addr, remote_port, input_delay, MAX_ROLLBACK_FRAMES);
|
int result = Netplay::Start(local_handle, local_port, remote_addr, remote_port, input_delay, MAX_ROLLBACK_FRAMES);
|
||||||
|
// notify that the session failed
|
||||||
if (result != GGPO_OK)
|
if (result != GGPO_OK)
|
||||||
{
|
|
||||||
Log_ErrorPrintf("Failed to Create Netplay Session! Error: %d", result);
|
Log_ErrorPrintf("Failed to Create Netplay Session! Error: %d", result);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Load savestate if available
|
||||||
|
std::string save = EmuFolders::SaveStates + "/netplay/" + System::GetRunningSerial() + ".sav";
|
||||||
|
System::LoadState(save.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -425,11 +474,12 @@ bool Netplay::NpBeginGameCb(void* ctx, const char* game_name)
|
||||||
StopNetplaySession();
|
StopNetplaySession();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Fast Forward to Game Start
|
|
||||||
SPU::SetAudioOutputMuted(true);
|
SPU::SetAudioOutputMuted(true);
|
||||||
|
// Fast Forward to Game Start if needed.
|
||||||
while (System::GetInternalFrameNumber() < 2)
|
while (System::GetInternalFrameNumber() < 2)
|
||||||
System::RunFrame();
|
System::RunFrame();
|
||||||
SPU::SetAudioOutputMuted(false);
|
SPU::SetAudioOutputMuted(false);
|
||||||
|
// Set Initial Frame Pacing
|
||||||
InitializeFramePacing();
|
InitializeFramePacing();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -446,24 +496,31 @@ bool Netplay::NpAdvFrameCb(void* ctx, int flags)
|
||||||
bool Netplay::NpSaveFrameCb(void* ctx, unsigned char** buffer, int* len, int* checksum, int frame)
|
bool Netplay::NpSaveFrameCb(void* ctx, unsigned char** buffer, int* len, int* checksum, int frame)
|
||||||
{
|
{
|
||||||
SaveStateBuffer our_buffer;
|
SaveStateBuffer our_buffer;
|
||||||
if (s_save_buffer_pool.empty())
|
// min size is 2 because otherwise the desync logger doesnt have enough time to dump the state.
|
||||||
|
if (s_save_buffer_pool.size() < 2)
|
||||||
{
|
{
|
||||||
our_buffer = std::make_unique<System::MemorySaveState>();
|
our_buffer = std::make_unique<System::MemorySaveState>();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
our_buffer = std::move(s_save_buffer_pool.back());
|
our_buffer = std::move(s_save_buffer_pool.front());
|
||||||
s_save_buffer_pool.pop_back();
|
s_save_buffer_pool.pop_front();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!System::SaveMemoryState(our_buffer.get()))
|
if (!System::SaveMemoryState(our_buffer.get()))
|
||||||
{
|
{
|
||||||
s_save_buffer_pool.push_back(std::move(our_buffer));
|
s_save_buffer_pool.push_front(std::move(our_buffer));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// desync detection
|
||||||
|
const u32 state_size = our_buffer.get()->state_stream.get()->GetMemorySize();
|
||||||
|
unsigned char* state = reinterpret_cast<unsigned char*>(our_buffer.get()->state_stream.get()->GetMemoryPointer());
|
||||||
|
GenerateChecksumForFrame(checksum, frame, state, state_size);
|
||||||
|
|
||||||
*len = sizeof(System::MemorySaveState);
|
*len = sizeof(System::MemorySaveState);
|
||||||
*buffer = reinterpret_cast<unsigned char*>(our_buffer.release());
|
*buffer = reinterpret_cast<unsigned char*>(our_buffer.release());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -475,8 +532,9 @@ bool Netplay::NpLoadFrameCb(void* ctx, unsigned char* buffer, int len, int rb_fr
|
||||||
return System::LoadMemoryState(*reinterpret_cast<const System::MemorySaveState*>(buffer));
|
return System::LoadMemoryState(*reinterpret_cast<const System::MemorySaveState*>(buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Netplay::NpFreeBuffCb(void* ctx, void* buffer)
|
void Netplay::NpFreeBuffCb(void* ctx, void* buffer, int frame)
|
||||||
{
|
{
|
||||||
|
// Log_VerbosePrintf("Reuse Buffer: %d", frame);
|
||||||
SaveStateBuffer our_buffer(reinterpret_cast<System::MemorySaveState*>(buffer));
|
SaveStateBuffer our_buffer(reinterpret_cast<System::MemorySaveState*>(buffer));
|
||||||
s_save_buffer_pool.push_back(std::move(our_buffer));
|
s_save_buffer_pool.push_back(std::move(our_buffer));
|
||||||
}
|
}
|
||||||
|
@ -484,7 +542,7 @@ void Netplay::NpFreeBuffCb(void* ctx, void* buffer)
|
||||||
bool Netplay::NpOnEventCb(void* ctx, GGPOEvent* ev)
|
bool Netplay::NpOnEventCb(void* ctx, GGPOEvent* ev)
|
||||||
{
|
{
|
||||||
char buff[128];
|
char buff[128];
|
||||||
std::string msg;
|
std::string msg, filename;
|
||||||
switch (ev->code)
|
switch (ev->code)
|
||||||
{
|
{
|
||||||
case GGPOEventCode::GGPO_EVENTCODE_CONNECTED_TO_PEER:
|
case GGPOEventCode::GGPO_EVENTCODE_CONNECTED_TO_PEER:
|
||||||
|
@ -523,10 +581,14 @@ bool Netplay::NpOnEventCb(void* ctx, GGPOEvent* ev)
|
||||||
HandleTimeSyncEvent(ev->u.timesync.frames_ahead, ev->u.timesync.timeSyncPeriodInFrames);
|
HandleTimeSyncEvent(ev->u.timesync.frames_ahead, ev->u.timesync.timeSyncPeriodInFrames);
|
||||||
break;
|
break;
|
||||||
case GGPOEventCode::GGPO_EVENTCODE_DESYNC:
|
case GGPOEventCode::GGPO_EVENTCODE_DESYNC:
|
||||||
sprintf(buff, "Netplay Desync Detected!: Frame: %d, L:%u, R:%u", ev->u.desync.nFrameOfDesync,
|
sprintf(buff, "Desync Detected: Current Frame: %d, Desync Frame: %d, Diff: %d, L:%u, R:%u", CurrentFrame(),
|
||||||
ev->u.desync.ourCheckSum, ev->u.desync.remoteChecksum);
|
ev->u.desync.nFrameOfDesync, CurrentFrame() - ev->u.desync.nFrameOfDesync, ev->u.desync.ourCheckSum,
|
||||||
|
ev->u.desync.remoteChecksum);
|
||||||
msg = buff;
|
msg = buff;
|
||||||
break;
|
GenerateDesyncReport(ev->u.desync.nFrameOfDesync);
|
||||||
|
Host::AddKeyedOSDMessage("Netplay", msg, 5);
|
||||||
|
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
sprintf(buff, "Netplay Event Code: %d", ev->code);
|
sprintf(buff, "Netplay Event Code: %d", ev->code);
|
||||||
msg = buff;
|
msg = buff;
|
||||||
|
|
|
@ -1752,6 +1752,23 @@ bool System::DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_di
|
||||||
cpu_overclock_active ?
|
cpu_overclock_active ?
|
||||||
Settings::CPUOverclockFractionToPercent(cpu_overclock_numerator, cpu_overclock_denominator) :
|
Settings::CPUOverclockFractionToPercent(cpu_overclock_numerator, cpu_overclock_denominator) :
|
||||||
100u);
|
100u);
|
||||||
|
|
||||||
|
// during netplay if a file savestate is loaded set
|
||||||
|
// the overclocks to the same value as the savestate
|
||||||
|
// file savestates are usually only loaded at game start
|
||||||
|
if (Netplay::IsActive() && !is_memory_state && cpu_overclock_active)
|
||||||
|
{
|
||||||
|
g_settings.cpu_overclock_enable = cpu_overclock_active;
|
||||||
|
g_settings.cpu_overclock_numerator = cpu_overclock_numerator;
|
||||||
|
g_settings.cpu_overclock_denominator = cpu_overclock_denominator;
|
||||||
|
|
||||||
|
g_settings.UpdateOverclockActive();
|
||||||
|
|
||||||
|
Host::AddFormattedOSDMessage(10.0f,
|
||||||
|
Host::TranslateString("OSDMessage", "WARNING: CPU overclock was changed to (%u%%) to match netplay savestate."),
|
||||||
|
g_settings.cpu_overclock_enable ? g_settings.GetCPUOverclockPercent() : 100u );
|
||||||
|
}
|
||||||
|
|
||||||
UpdateOverclock();
|
UpdateOverclock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1466,7 +1466,7 @@ void EmuThread::run()
|
||||||
// main loop
|
// main loop
|
||||||
while (!m_shutdown_flag)
|
while (!m_shutdown_flag)
|
||||||
{
|
{
|
||||||
if (Netplay::IsActive())
|
if (Netplay::IsActive() && System::IsRunning())
|
||||||
{
|
{
|
||||||
Netplay::ExecuteNetplay();
|
Netplay::ExecuteNetplay();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue