From 0cab94fb12bbd5ee96f814ed7fe8d15eba83d299 Mon Sep 17 00:00:00 2001 From: Kawe Mazidjatari <48657826+Mauler125@users.noreply.github.com> Date: Sat, 30 Dec 2023 01:17:39 +0100 Subject: [PATCH] Server: sync client's clock with server's This should in theory fix the server and client desync that takes place after multiple hours of uptime without disconnecting or reloading. Note that in this version of the engine the server tick is only send on connect (Titanfall 2 does seem to send it every tick, as this netmsg is also responsible for updating the server percentage on the 'cl_showfps' HUD, which does work as designed). The patched code still writes a 'statistics only' server tick update every tick, but only sends full if the interval time has been reached for that particular client instance. --- r5dev/common/global.cpp | 4 +++ r5dev/common/global.h | 2 ++ r5dev/engine/networkstringtable.cpp | 33 +++++++++++++++++++++--- r5dev/engine/server/vengineserver_impl.h | 4 +++ 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/r5dev/common/global.cpp b/r5dev/common/global.cpp index a94d33da..0226c48c 100644 --- a/r5dev/common/global.cpp +++ b/r5dev/common/global.cpp @@ -129,6 +129,8 @@ ConVar* sv_maxunlag = nullptr; ConVar* sv_unlag_clamp = nullptr; ConVar* sv_clockcorrection_msecs = nullptr; +ConVar* sv_clockSyncInterval = nullptr; + ConVar* sv_updaterate_sp = nullptr; ConVar* sv_updaterate_mp = nullptr; @@ -377,6 +379,8 @@ void ConVar_StaticInit(void) sv_unlag_clamp = ConVar::StaticCreate("sv_unlag_clamp", "0", FCVAR_RELEASE, "Clamp the difference between the current time and received command time to sv_maxunlag + sv_clockcorrection_msecs.", false, 0.f, false, 0.f, nullptr, nullptr); + sv_clockSyncInterval = ConVar::StaticCreate("sv_clockSyncInterval", "5.0", FCVAR_RELEASE, "Interval in seconds in which the clients's clock will be synced with that of the server's.", false, 0.f, false, 0.f, nullptr, nullptr); + sv_showconnecting = ConVar::StaticCreate("sv_showconnecting" , "1", FCVAR_RELEASE, "Logs information about the connecting client to the console.", false, 0.f, false, 0.f, nullptr, nullptr); sv_globalBanlist = ConVar::StaticCreate("sv_globalBanlist" , "1", FCVAR_RELEASE, "Determines whether or not to use the global banned list.", false, 0.f, false, 0.f, nullptr, "0 = Disable, 1 = Enable."); sv_pylonVisibility = ConVar::StaticCreate("sv_pylonVisibility", "0", FCVAR_RELEASE, "Determines the visibility to the Pylon master server.", false, 0.f, false, 0.f, nullptr, "0 = Offline, 1 = Hidden, 2 = Public."); diff --git a/r5dev/common/global.h b/r5dev/common/global.h index 3ae5c83e..bc86b60b 100644 --- a/r5dev/common/global.h +++ b/r5dev/common/global.h @@ -118,6 +118,8 @@ extern ConVar* sv_maxunlag; extern ConVar* sv_unlag_clamp; extern ConVar* sv_clockcorrection_msecs; +extern ConVar* sv_clockSyncInterval; + extern ConVar* sv_updaterate_sp; extern ConVar* sv_updaterate_mp; diff --git a/r5dev/engine/networkstringtable.cpp b/r5dev/engine/networkstringtable.cpp index 44647931..789bdf5b 100644 --- a/r5dev/engine/networkstringtable.cpp +++ b/r5dev/engine/networkstringtable.cpp @@ -93,20 +93,47 @@ bool CNetworkStringTable::Lock(bool bLock) void CNetworkStringTableContainer::WriteUpdateMessage(CNetworkStringTableContainer* thisp, CClient* pClient, unsigned int nTickAck, bf_write* pMsg) { #ifndef CLIENT_DLL - if (sv_stats->GetBool()) + const double currentTime = Plat_FloatTime(); + + ServerPlayer_t& clientExtended = g_ServerPlayer[pClient->GetUserID()]; + int commandTick = -1; // -1 means we update statistics only; see 'CClientState::VProcessServerTick()'. + + // NOTE: if we send this message each tick, the client will start to + // falter. Unlike other source games, we have to have some delay in + // between each server tick message for this to work correctly. + if (clientExtended.m_bRetryClockSync || + (currentTime - clientExtended.m_flLastClockSyncTime) > sv_clockSyncInterval->GetFloat()) { - uint8_t nCPUPercentage = static_cast(g_pServer->GetCPUUsage() * 100.0f); + // Sync the clocks on the client with that of the server's. + commandTick = pClient->GetCommandTick(); + clientExtended.m_flLastClockSyncTime = currentTime; + clientExtended.m_bRetryClockSync = false; + } + + // If commandTick == statistics only while server opted out, do not + // send the message. + const bool shouldSend = (commandTick == -1 && !sv_stats->GetBool()) ? false : true; + + if (shouldSend) + { + const uint8_t nCPUPercentage = static_cast(g_pServer->GetCPUUsage() * 100.0f); SVC_ServerTick serverTick(g_pServer->GetTick(), *host_frametime_unbounded, *host_frametime_stddeviation, nCPUPercentage); serverTick.m_nGroup = 0; serverTick.m_bReliable = true; - serverTick.m_NetTick.m_nCommandTick = -1; // Statistics only, see 'CClientState::VProcessServerTick'. + serverTick.m_NetTick.m_nCommandTick = commandTick; pMsg->WriteUBitLong(serverTick.GetType(), NETMSG_TYPE_BITS); + if (!pMsg->IsOverflowed()) { serverTick.WriteToBuffer(pMsg); } + else + { + Assert(0, "Snapshot buffer overflowed before string table update!"); + clientExtended.m_bRetryClockSync = true; // Retry on next snapshot for this client. + } } #endif // !CLIENT_DLL v_CNetworkStringTableContainer__WriteUpdateMessage(thisp, pClient, nTickAck, pMsg); diff --git a/r5dev/engine/server/vengineserver_impl.h b/r5dev/engine/server/vengineserver_impl.h index 5050fba6..66b97003 100644 --- a/r5dev/engine/server/vengineserver_impl.h +++ b/r5dev/engine/server/vengineserver_impl.h @@ -20,15 +20,19 @@ struct ServerPlayer_t { m_flCurrentNetProcessTime = 0.0; m_flLastNetProcessTime = 0.0; + m_flLastClockSyncTime = 0.0; m_flStringCommandQuotaTimeStart = 0.0; m_nStringCommandQuotaCount = NULL; + m_bRetryClockSync = false; m_bInitialConVarsSet = false; } double m_flCurrentNetProcessTime; double m_flLastNetProcessTime; + double m_flLastClockSyncTime; double m_flStringCommandQuotaTimeStart; int m_nStringCommandQuotaCount; + bool m_bRetryClockSync; bool m_bInitialConVarsSet; };