Merge pull request #2517 from comex/netplay-timebase-rb
[rebased] Compare timebase of netplay users to detect desyncs.
This commit is contained in:
commit
5c7caf1f22
|
@ -29,6 +29,7 @@
|
||||||
#include "Core/Host.h"
|
#include "Core/Host.h"
|
||||||
#include "Core/MemTools.h"
|
#include "Core/MemTools.h"
|
||||||
#include "Core/Movie.h"
|
#include "Core/Movie.h"
|
||||||
|
#include "Core/NetPlayClient.h"
|
||||||
#include "Core/NetPlayProto.h"
|
#include "Core/NetPlayProto.h"
|
||||||
#include "Core/PatchEngine.h"
|
#include "Core/PatchEngine.h"
|
||||||
#include "Core/State.h"
|
#include "Core/State.h"
|
||||||
|
@ -129,6 +130,12 @@ void SetIsFramelimiterTempDisabled(bool disable)
|
||||||
std::string GetStateFileName() { return s_state_filename; }
|
std::string GetStateFileName() { return s_state_filename; }
|
||||||
void SetStateFileName(const std::string& val) { s_state_filename = val; }
|
void SetStateFileName(const std::string& val) { s_state_filename = val; }
|
||||||
|
|
||||||
|
void FrameUpdateOnCPUThread()
|
||||||
|
{
|
||||||
|
if (NetPlay::IsNetPlayRunning())
|
||||||
|
NetPlayClient::SendTimeBase();
|
||||||
|
}
|
||||||
|
|
||||||
// Display messages and return values
|
// Display messages and return values
|
||||||
|
|
||||||
// Formatted stop message
|
// Formatted stop message
|
||||||
|
|
|
@ -63,6 +63,8 @@ void SetStateFileName(const std::string& val);
|
||||||
|
|
||||||
void SetBlockStart(u32 addr);
|
void SetBlockStart(u32 addr);
|
||||||
|
|
||||||
|
void FrameUpdateOnCPUThread();
|
||||||
|
|
||||||
bool ShouldSkipFrame(int skipped);
|
bool ShouldSkipFrame(int skipped);
|
||||||
void VideoThrottle();
|
void VideoThrottle();
|
||||||
void RequestRefreshInfo();
|
void RequestRefreshInfo();
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "Core/Core.h"
|
#include "Core/Core.h"
|
||||||
#include "Core/CoreTiming.h"
|
#include "Core/CoreTiming.h"
|
||||||
#include "Core/Movie.h"
|
#include "Core/Movie.h"
|
||||||
|
#include "Core/NetPlayClient.h"
|
||||||
#include "Core/NetPlayProto.h"
|
#include "Core/NetPlayProto.h"
|
||||||
#include "Core/State.h"
|
#include "Core/State.h"
|
||||||
#include "Core/DSP/DSPCore.h"
|
#include "Core/DSP/DSPCore.h"
|
||||||
|
@ -139,6 +140,8 @@ std::string GetInputDisplay()
|
||||||
|
|
||||||
void FrameUpdate()
|
void FrameUpdate()
|
||||||
{
|
{
|
||||||
|
// TODO[comex]: This runs on the GPU thread, yet it messes with the CPU
|
||||||
|
// state directly. That's super sketchy.
|
||||||
g_currentFrame++;
|
g_currentFrame++;
|
||||||
if (!s_bPolled)
|
if (!s_bPolled)
|
||||||
g_currentLagCount++;
|
g_currentLagCount++;
|
||||||
|
|
|
@ -442,6 +442,26 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case NP_MSG_DESYNC_DETECTED:
|
||||||
|
{
|
||||||
|
int pid_to_blame;
|
||||||
|
u32 frame;
|
||||||
|
packet >> pid_to_blame;
|
||||||
|
packet >> frame;
|
||||||
|
const char* blame_str = "";
|
||||||
|
const char* blame_name = "";
|
||||||
|
std::lock_guard<std::recursive_mutex> lkp(m_crit.players);
|
||||||
|
if (pid_to_blame != -1)
|
||||||
|
{
|
||||||
|
auto it = m_players.find(pid_to_blame);
|
||||||
|
blame_str = " from player ";
|
||||||
|
blame_name = it != m_players.end() ? it->second.name.c_str() : "??";
|
||||||
|
}
|
||||||
|
|
||||||
|
m_dialog->AppendChat(StringFromFormat("/!\\ Possible desync detected%s%s on frame %u", blame_str, blame_name, frame));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
PanicAlertT("Unknown message received with id : %d", mid);
|
PanicAlertT("Unknown message received with id : %d", mid);
|
||||||
break;
|
break;
|
||||||
|
@ -643,6 +663,8 @@ bool NetPlayClient::StartGame(const std::string &path)
|
||||||
|
|
||||||
m_dialog->AppendChat(" -- STARTING GAME -- ");
|
m_dialog->AppendChat(" -- STARTING GAME -- ");
|
||||||
|
|
||||||
|
m_timebase_frame = 0;
|
||||||
|
|
||||||
m_is_running.store(true);
|
m_is_running.store(true);
|
||||||
NetPlay_Enable(this);
|
NetPlay_Enable(this);
|
||||||
|
|
||||||
|
@ -1044,6 +1066,20 @@ u8 NetPlayClient::LocalWiimoteToInGameWiimote(u8 local_pad)
|
||||||
return ingame_pad;
|
return ingame_pad;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NetPlayClient::SendTimeBase()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(crit_netplay_client);
|
||||||
|
|
||||||
|
u64 timebase = SystemTimers::GetFakeTimeBase();
|
||||||
|
|
||||||
|
sf::Packet* spac = new sf::Packet;
|
||||||
|
*spac << (MessageId)NP_MSG_TIMEBASE;
|
||||||
|
*spac << (u32)timebase;
|
||||||
|
*spac << (u32)(timebase << 32);
|
||||||
|
*spac << netplay_client->m_timebase_frame++;
|
||||||
|
netplay_client->SendAsync(spac);
|
||||||
|
}
|
||||||
|
|
||||||
// stuff hacked into dolphin
|
// stuff hacked into dolphin
|
||||||
|
|
||||||
// called from ---CPU--- thread
|
// called from ---CPU--- thread
|
||||||
|
|
|
@ -77,6 +77,8 @@ public:
|
||||||
|
|
||||||
u8 LocalWiimoteToInGameWiimote(u8 local_pad);
|
u8 LocalWiimoteToInGameWiimote(u8 local_pad);
|
||||||
|
|
||||||
|
static void SendTimeBase();
|
||||||
|
|
||||||
enum State
|
enum State
|
||||||
{
|
{
|
||||||
WaitingForTraversalClientConnection,
|
WaitingForTraversalClientConnection,
|
||||||
|
@ -141,6 +143,8 @@ private:
|
||||||
std::string m_player_name;
|
std::string m_player_name;
|
||||||
bool m_connecting;
|
bool m_connecting;
|
||||||
TraversalClient* m_traversal_client;
|
TraversalClient* m_traversal_client;
|
||||||
|
|
||||||
|
u32 m_timebase_frame;
|
||||||
};
|
};
|
||||||
|
|
||||||
void NetPlay_Enable(NetPlayClient* const np);
|
void NetPlay_Enable(NetPlayClient* const np);
|
||||||
|
|
|
@ -29,7 +29,7 @@ struct Rpt : public std::vector<u8>
|
||||||
|
|
||||||
typedef std::vector<u8> NetWiimote;
|
typedef std::vector<u8> NetWiimote;
|
||||||
|
|
||||||
#define NETPLAY_VERSION "Dolphin NetPlay 2014-01-08"
|
#define NETPLAY_VERSION "Dolphin NetPlay 2015-03-10"
|
||||||
|
|
||||||
extern u64 g_netplay_initial_gctime;
|
extern u64 g_netplay_initial_gctime;
|
||||||
|
|
||||||
|
@ -53,6 +53,9 @@ enum
|
||||||
NP_MSG_STOP_GAME = 0xA2,
|
NP_MSG_STOP_GAME = 0xA2,
|
||||||
NP_MSG_DISABLE_GAME = 0xA3,
|
NP_MSG_DISABLE_GAME = 0xA3,
|
||||||
|
|
||||||
|
NP_MSG_TIMEBASE = 0xB0,
|
||||||
|
NP_MSG_DESYNC_DETECTED = 0xB1,
|
||||||
|
|
||||||
NP_MSG_READY = 0xD0,
|
NP_MSG_READY = 0xD0,
|
||||||
NP_MSG_NOT_READY = 0xD1,
|
NP_MSG_NOT_READY = 0xD1,
|
||||||
|
|
||||||
|
|
|
@ -110,7 +110,6 @@ void NetPlayServer::ThreadFunc()
|
||||||
while (m_do_loop)
|
while (m_do_loop)
|
||||||
{
|
{
|
||||||
// update pings every so many seconds
|
// update pings every so many seconds
|
||||||
|
|
||||||
if ((m_ping_timer.GetTimeElapsed() > 1000) || m_update_pings)
|
if ((m_ping_timer.GetTimeElapsed() > 1000) || m_update_pings)
|
||||||
{
|
{
|
||||||
m_ping_key = Common::Timer::GetTimeMs();
|
m_ping_key = Common::Timer::GetTimeMs();
|
||||||
|
@ -581,6 +580,53 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case NP_MSG_TIMEBASE:
|
||||||
|
{
|
||||||
|
u32 x, y, frame;
|
||||||
|
packet >> x;
|
||||||
|
packet >> y;
|
||||||
|
packet >> frame;
|
||||||
|
|
||||||
|
if (m_desync_detected)
|
||||||
|
break;
|
||||||
|
|
||||||
|
u64 timebase = x | ((u64)y << 32);
|
||||||
|
std::vector<std::pair<PlayerId, u64>>& timebases = m_timebase_by_frame[frame];
|
||||||
|
timebases.emplace_back(player.pid, timebase);
|
||||||
|
if (timebases.size() >= m_players.size())
|
||||||
|
{
|
||||||
|
// we have all records for this frame
|
||||||
|
|
||||||
|
if (!std::all_of(timebases.begin(), timebases.end(), [&](std::pair<PlayerId, u64> pair){ return pair.second == timebases[0].second; }))
|
||||||
|
{
|
||||||
|
int pid_to_blame = -1;
|
||||||
|
if (timebases.size() > 2)
|
||||||
|
{
|
||||||
|
for (auto pair : timebases)
|
||||||
|
{
|
||||||
|
if (std::all_of(timebases.begin(), timebases.end(), [&](std::pair<PlayerId, u64> other) {
|
||||||
|
return other.first == pair.first || other.second != pair.second;
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
// we are the only outlier
|
||||||
|
pid_to_blame = pair.first;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sf::Packet spac;
|
||||||
|
spac << (MessageId) NP_MSG_DESYNC_DETECTED;
|
||||||
|
spac << pid_to_blame;
|
||||||
|
spac << frame;
|
||||||
|
SendToClients(spac);
|
||||||
|
|
||||||
|
m_desync_detected = true;
|
||||||
|
}
|
||||||
|
m_timebase_by_frame.erase(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
PanicAlertT("Unknown message with id:%d received from player:%d Kicking player!", mid, player.pid);
|
PanicAlertT("Unknown message with id:%d received from player:%d Kicking player!", mid, player.pid);
|
||||||
// unknown message, kick the client
|
// unknown message, kick the client
|
||||||
|
@ -635,6 +681,8 @@ void NetPlayServer::SetNetSettings(const NetSettings &settings)
|
||||||
// called from ---GUI--- thread
|
// called from ---GUI--- thread
|
||||||
bool NetPlayServer::StartGame()
|
bool NetPlayServer::StartGame()
|
||||||
{
|
{
|
||||||
|
m_timebase_by_frame.clear();
|
||||||
|
m_desync_detected = false;
|
||||||
std::lock_guard<std::recursive_mutex> lkg(m_crit.game);
|
std::lock_guard<std::recursive_mutex> lkg(m_crit.game);
|
||||||
m_current_game = Common::Timer::GetTimeMs();
|
m_current_game = Common::Timer::GetTimeMs();
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <SFML/Network/Packet.hpp>
|
#include <SFML/Network/Packet.hpp>
|
||||||
#include "Common/Timer.h"
|
#include "Common/Timer.h"
|
||||||
|
@ -99,6 +100,9 @@ private:
|
||||||
|
|
||||||
std::map<PlayerId, Client> m_players;
|
std::map<PlayerId, Client> m_players;
|
||||||
|
|
||||||
|
std::unordered_map<u32, std::vector<std::pair<PlayerId, u64>>> m_timebase_by_frame;
|
||||||
|
bool m_desync_detected;
|
||||||
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
std::recursive_mutex game;
|
std::recursive_mutex game;
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "Common/ChunkFile.h"
|
#include "Common/ChunkFile.h"
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Core/ConfigManager.h"
|
#include "Core/ConfigManager.h"
|
||||||
|
#include "Core/Core.h"
|
||||||
#include "Core/CoreTiming.h"
|
#include "Core/CoreTiming.h"
|
||||||
#include "Core/State.h"
|
#include "Core/State.h"
|
||||||
#include "Core/HW/MMIO.h"
|
#include "Core/HW/MMIO.h"
|
||||||
|
@ -282,6 +283,8 @@ void SetFinish_OnMainThread(u64 userdata, int cyclesLate)
|
||||||
s_signal_finish_interrupt.store(1);
|
s_signal_finish_interrupt.store(1);
|
||||||
UpdateInterrupts();
|
UpdateInterrupts();
|
||||||
CommandProcessor::SetInterruptFinishWaiting(false);
|
CommandProcessor::SetInterruptFinishWaiting(false);
|
||||||
|
|
||||||
|
Core::FrameUpdateOnCPUThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetToken
|
// SetToken
|
||||||
|
|
Loading…
Reference in New Issue