Netplay / Timesync:
Improved timesync algorithm. Prevents spiraling into a feedback loop of negativity.
This commit is contained in:
parent
de9c16bb73
commit
1b92f091be
|
@ -49,6 +49,8 @@ static void Close();
|
|||
static void AdvanceFrame(u16 checksum = 0);
|
||||
static void RunFrame();
|
||||
|
||||
static s32 CurrentFrame();
|
||||
|
||||
static void NetplayAdvanceFrame(Netplay::Input inputs[], int disconnect_flags);
|
||||
|
||||
/// Frame Pacing
|
||||
|
@ -63,7 +65,6 @@ static void Throttle();
|
|||
static MemorySettingsInterface s_settings_overlay;
|
||||
|
||||
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{};
|
||||
|
@ -77,6 +78,7 @@ static std::array<std::array<float, 32>, NUM_CONTROLLER_AND_CARD_PORTS> s_net_in
|
|||
static float s_target_speed = 1.0f;
|
||||
static Common::Timer::Value s_frame_period = 0;
|
||||
static Common::Timer::Value s_next_frame_time = 0;
|
||||
static s32 s_next_timesync_recovery_frame = -1;
|
||||
|
||||
} // namespace Netplay
|
||||
|
||||
|
@ -87,7 +89,6 @@ s32 Netplay::Start(s32 lhandle, u16 lport, std::string& raddr, u16 rport, s32 ld
|
|||
SetSettings();
|
||||
InitializeFramePacing();
|
||||
|
||||
s_max_pred = pred;
|
||||
/*
|
||||
TODO: since saving every frame during rollback loses us time to do actual gamestate iterations it might be better to
|
||||
hijack the update / save / load cycle to only save every confirmed frame only saving when actually needed.
|
||||
|
@ -103,7 +104,8 @@ s32 Netplay::Start(s32 lhandle, u16 lport, std::string& raddr, u16 rport, s32 ld
|
|||
|
||||
GGPOErrorCode result;
|
||||
|
||||
result = ggpo_start_session(&s_ggpo, &cb, "Duckstation-Netplay", 2, sizeof(Netplay::Input), lport, s_max_pred);
|
||||
result =
|
||||
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);
|
||||
|
||||
ggpo_set_disconnect_timeout(s_ggpo, 3000);
|
||||
|
@ -143,7 +145,6 @@ void Netplay::Close()
|
|||
s_ggpo = nullptr;
|
||||
s_save_buffer_pool.clear();
|
||||
s_local_handle = GGPO_INVALID_HANDLE;
|
||||
s_max_pred = 0;
|
||||
|
||||
// Restore original settings.
|
||||
Host::Internal::SetNetplaySettingsLayer(nullptr);
|
||||
|
@ -198,16 +199,38 @@ void Netplay::UpdateThrottlePeriod()
|
|||
|
||||
void Netplay::HandleTimeSyncEvent(float frame_delta, int update_interval)
|
||||
{
|
||||
// Distribute the frame difference over the next N frames.
|
||||
s_target_speed = 1.0f + -(frame_delta / static_cast<float>(update_interval));
|
||||
// threshold to what is is with correcting for.
|
||||
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.
|
||||
// otherwise we will keep spiraling into unplayable gameplay.
|
||||
float added_time_per_frame = -(total_time / (static_cast<float>(update_interval) * 0.8f));
|
||||
float iterations_per_frame = 1.0f / s_frame_period;
|
||||
|
||||
s_target_speed = (s_frame_period + added_time_per_frame) * iterations_per_frame;
|
||||
s_next_timesync_recovery_frame = CurrentFrame() + std::ceil(static_cast<float>(update_interval) * 0.8f);
|
||||
|
||||
UpdateThrottlePeriod();
|
||||
|
||||
Log_DevPrintf("TimeSync: %f frames %s, target speed %.4f%%", std::abs(frame_delta),
|
||||
(frame_delta >= 0.0f ? "ahead" : "behind"), s_target_speed * 100.0f);
|
||||
Log_VerbosePrintf("TimeSync: %f frames %s, target speed %.4f%%", std::abs(frame_delta),
|
||||
(frame_delta >= 0.0f ? "ahead" : "behind"), s_target_speed * 100.0f);
|
||||
}
|
||||
|
||||
void Netplay::Throttle()
|
||||
{
|
||||
// if the s_next_timesync_recovery_frame has been reached revert back to the normal throttle speed
|
||||
s32 current_frame = CurrentFrame();
|
||||
if (s_target_speed != 1.0f && current_frame >= s_next_timesync_recovery_frame)
|
||||
{
|
||||
s_target_speed = 1.0f;
|
||||
UpdateThrottlePeriod();
|
||||
|
||||
Log_VerbosePrintf("TimeSync Recovery: frame %d, target speed %.4f%%", current_frame, s_target_speed * 100.0f);
|
||||
}
|
||||
|
||||
s_next_frame_time += s_frame_period;
|
||||
|
||||
// If we're running too slow, advance the next frame time based on the time we lost. Effectively skips
|
||||
|
@ -220,7 +243,6 @@ void Netplay::Throttle()
|
|||
s_next_frame_time += (diff / s_frame_period) * s_frame_period;
|
||||
return;
|
||||
}
|
||||
|
||||
// Poll at 2ms throughout the sleep.
|
||||
// This way the network traffic comes through as soon as possible.
|
||||
const Common::Timer::Value sleep_period = Common::Timer::ConvertMillisecondsToValue(1);
|
||||
|
@ -249,9 +271,9 @@ void Netplay::AdvanceFrame(u16 checksum)
|
|||
void Netplay::RunFrame()
|
||||
{
|
||||
// run game
|
||||
bool needIdle = true;
|
||||
bool need_idle = true;
|
||||
auto result = GGPO_OK;
|
||||
int disconnectFlags = 0;
|
||||
int disconnect_flags = 0;
|
||||
Netplay::Input inputs[2] = {};
|
||||
// add local input
|
||||
if (s_local_handle != GGPO_INVALID_HANDLE)
|
||||
|
@ -262,21 +284,28 @@ void Netplay::RunFrame()
|
|||
// advance game
|
||||
if (GGPO_SUCCEEDED(result))
|
||||
{
|
||||
result = SyncInput(inputs, &disconnectFlags);
|
||||
result = SyncInput(inputs, &disconnect_flags);
|
||||
if (GGPO_SUCCEEDED(result))
|
||||
{
|
||||
// enable again when rolling back done
|
||||
SPU::SetAudioOutputMuted(false);
|
||||
NetplayAdvanceFrame(inputs, disconnectFlags);
|
||||
NetplayAdvanceFrame(inputs, disconnect_flags);
|
||||
// coming here means that the system doesnt need to idle anymore
|
||||
needIdle = false;
|
||||
need_idle = false;
|
||||
}
|
||||
}
|
||||
// allow ggpo to do housekeeping if needed
|
||||
if (needIdle)
|
||||
if (need_idle)
|
||||
ggpo_idle(s_ggpo);
|
||||
}
|
||||
|
||||
s32 Netplay::CurrentFrame()
|
||||
{
|
||||
s32 current = -1;
|
||||
ggpo_get_current_frame(s_ggpo, current);
|
||||
return current;
|
||||
}
|
||||
|
||||
void Netplay::CollectInput(u32 slot, u32 bind, float value)
|
||||
{
|
||||
s_net_input[slot][bind] = value;
|
||||
|
@ -318,7 +347,7 @@ s32 Netplay::GetPing()
|
|||
|
||||
u32 Netplay::GetMaxPrediction()
|
||||
{
|
||||
return s_max_pred;
|
||||
return MAX_ROLLBACK_FRAMES;
|
||||
}
|
||||
|
||||
void Netplay::SetInputs(Netplay::Input inputs[2])
|
||||
|
@ -326,9 +355,9 @@ void Netplay::SetInputs(Netplay::Input inputs[2])
|
|||
for (u32 i = 0; i < 2; i++)
|
||||
{
|
||||
auto cont = Pad::GetController(i);
|
||||
std::bitset<sizeof(u32) * 8> buttonBits(inputs[i].button_data);
|
||||
std::bitset<sizeof(u32) * 8> button_bits(inputs[i].button_data);
|
||||
for (u32 j = 0; j < (u32)DigitalController::Button::Count; j++)
|
||||
cont->SetBindState(j, buttonBits.test(j) ? 1.0f : 0.0f);
|
||||
cont->SetBindState(j, button_bits.test(j) ? 1.0f : 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -341,7 +370,7 @@ void Netplay::StartNetplaySession(s32 local_handle, u16 local_port, std::string&
|
|||
// set game path for later loading during the begin game callback
|
||||
s_game_path = std::move(game_path);
|
||||
// create session
|
||||
int result = Netplay::Start(local_handle, local_port, remote_addr, remote_port, input_delay, 8);
|
||||
int result = Netplay::Start(local_handle, local_port, remote_addr, remote_port, input_delay, MAX_ROLLBACK_FRAMES);
|
||||
if (result != GGPO_OK)
|
||||
{
|
||||
Log_ErrorPrintf("Failed to Create Netplay Session! Error: %d", result);
|
||||
|
@ -406,9 +435,9 @@ bool Netplay::NpBeginGameCb(void* ctx, const char* game_name)
|
|||
bool Netplay::NpAdvFrameCb(void* ctx, int flags)
|
||||
{
|
||||
Netplay::Input inputs[2] = {};
|
||||
int disconnectFlags;
|
||||
Netplay::SyncInput(inputs, &disconnectFlags);
|
||||
NetplayAdvanceFrame(inputs, disconnectFlags);
|
||||
int disconnect_flags;
|
||||
Netplay::SyncInput(inputs, &disconnect_flags);
|
||||
NetplayAdvanceFrame(inputs, disconnect_flags);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@ enum : u32
|
|||
{
|
||||
// Maximum number of emulated controllers.
|
||||
MAX_PLAYERS = 2,
|
||||
// Maximum netplay prediction frames
|
||||
MAX_ROLLBACK_FRAMES = 8,
|
||||
};
|
||||
|
||||
void StartNetplaySession(s32 local_handle, u16 local_port, std::string& remote_addr, u16 remote_port, s32 input_delay,
|
||||
|
|
Loading…
Reference in New Issue