From b383302128a4ff060fbbdd38a37c309041906c9f Mon Sep 17 00:00:00 2001 From: Asher Baker Date: Mon, 19 Jul 2021 19:12:09 +0100 Subject: [PATCH] Switch internal SM concept of frames to use Think (#1540) This has been asked for and debated in some form since Valve introduced hibernation into the Source engine. The changes here are based on quite a deep dive into the engine's frame/think logic (mainly in CS:GO which has "legacy" hibernation and TF2 which has modern "frameless" ticking) and all seem to be sane. I think I've managed to maintain all the oddities around time keeping, and the simulated bool (even though we don't really use it for anything) should have a sane value. There is a slight behaviour change for anything needing exact timings as we're now run earlier in the frame before gpGlobals are updated, this should generally be fine but it might affect some plugins such as bhop timers that are trying to be extremely precise (often more precise than the underlying data they're using). We'll probably want to add a native for plugins to detect if the server is not completely simulating so they can opt out of work, but I think defaulting to having things work like this makes more sense than adding a 2nd set of per-frame forwards and natives (#540), and this makes timers and any extension callbacks work automatically. --- core/TimerSys.cpp | 47 +++++++++++++++++++++++++++------------------- core/TimerSys.h | 3 +++ core/sourcemod.cpp | 6 ++---- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/core/TimerSys.cpp b/core/TimerSys.cpp index 2084b153..abb6a60c 100644 --- a/core/TimerSys.cpp +++ b/core/TimerSys.cpp @@ -35,6 +35,7 @@ #include "frame_hooks.h" #include "ConVarManager.h" #include "logic_bridge.h" +#include #define TIMER_MIN_ACCURACY 0.1 @@ -170,9 +171,8 @@ void ITimer::Initialize(ITimedEvent *pCallbacks, float fInterval, float fToExec, TimerSystem::TimerSystem() { m_pMapTimer = NULL; - m_bHasMapTickedYet = false; - m_bHasMapSimulatedYet = false; m_fLastTickedTime = 0.0f; + OnSourceModLevelEnd(); } TimerSystem::~TimerSystem() @@ -213,29 +213,28 @@ void TimerSystem::OnSourceModLevelEnd() { m_bHasMapTickedYet = false; m_bHasMapSimulatedYet = false; + m_bWasSimulating = false; + m_uFramesAhead = 0; } -void TimerSystem::GameFrame(bool simulating) +/* Think is called before gpGlobals is updated every frame, even if the server is hibernating */ +void TimerSystem::Think(bool unused) { - if (simulating && m_bHasMapTickedYet) - { - g_fUniversalTime += gpGlobals->curtime - m_fLastTickedTime; - if (!m_bHasMapSimulatedYet) - { - m_bHasMapSimulatedYet = true; - MapTimeLeftChanged(); - } - } - else - { + m_uFramesAhead++; + bool simulating = m_bWasSimulating && m_uFramesAhead == 1; + + if (m_bHasMapTickedYet) { + g_fUniversalTime += gpGlobals->realtime - m_fLastTickedTime; + } else { g_fUniversalTime += gpGlobals->interval_per_tick; } - m_fLastTickedTime = gpGlobals->curtime; + m_fLastTickedTime = gpGlobals->realtime; m_bHasMapTickedYet = true; - if (g_fUniversalTime >= g_fTimerThink) - { + logicore.callbacks->OnThink(simulating); + + if (g_fUniversalTime >= g_fTimerThink) { RunFrame(); g_fTimerThink = CalcNextThink(g_fTimerThink, TIMER_MIN_ACCURACY); @@ -243,9 +242,19 @@ void TimerSystem::GameFrame(bool simulating) RunFrameHooks(simulating); - if (m_pOnGameFrame->GetFunctionCount()) + m_pOnGameFrame->Execute(); +} + +/* GameFrame is called after gpGlobals is updated, and may not be called when the server is hibernating */ +void TimerSystem::GameFrame(bool simulating) +{ + m_bWasSimulating = simulating; + m_uFramesAhead = 0; + + if (simulating && !m_bHasMapSimulatedYet) { - m_pOnGameFrame->Execute(NULL); + m_bHasMapSimulatedYet = true; + MapTimeLeftChanged(); } } diff --git a/core/TimerSys.h b/core/TimerSys.h index c5a9b758..62ee5e58 100644 --- a/core/TimerSys.h +++ b/core/TimerSys.h @@ -82,6 +82,7 @@ public: //ITimerSystem public: void RunFrame(); void RemoveMapChangeTimers(); + void Think(bool unused); void GameFrame(bool simulating); private: List m_SingleTimers; @@ -92,6 +93,8 @@ private: /* This is stuff for our manual ticking escapades. */ bool m_bHasMapTickedYet; /** Has the map ticked yet? */ bool m_bHasMapSimulatedYet; /** Has the map simulated yet? */ + bool m_bWasSimulating; /** Was the last GameFrame simulating */ + unsigned m_uFramesAhead; /** Number of frames Think is ahead of GameFrame */ float m_fLastTickedTime; /** Last time that the game currently gave us while ticking. */ diff --git a/core/sourcemod.cpp b/core/sourcemod.cpp index eb55ed75..a46cd1cb 100644 --- a/core/sourcemod.cpp +++ b/core/sourcemod.cpp @@ -46,7 +46,6 @@ #include #include #include -#include #include SH_DECL_HOOK6(IServerGameDLL, LevelInit, SH_NOATTRIB, false, bool, const char *, const char *, const char *, const char *, bool, bool); @@ -291,6 +290,7 @@ bool SourceModBase::InitializeSourceMod(char *error, size_t maxlength, bool late void SourceModBase::StartSourceMod(bool late) { SH_ADD_HOOK(IServerGameDLL, LevelShutdown, gamedll, SH_MEMBER(this, &SourceModBase::LevelShutdown), false); + SH_ADD_HOOK(IServerGameDLL, Think, gamedll, SH_MEMBER(&g_Timers, &TimerSystem::Think), false); SH_ADD_HOOK(IServerGameDLL, GameFrame, gamedll, SH_MEMBER(&g_Timers, &TimerSystem::GameFrame), false); enginePatch = SH_GET_CALLCLASS(engine); @@ -362,8 +362,6 @@ void SourceModBase::StartSourceMod(bool late) { g_pSourcePawn2->InstallWatchdogTimer(atoi(timeout) * 1000); } - - SH_ADD_HOOK(IServerGameDLL, Think, gamedll, SH_MEMBER(logicore.callbacks, &IProviderCallbacks::OnThink), false); } static bool g_LevelEndBarrier = false; @@ -598,8 +596,8 @@ void SourceModBase::ShutdownServices() } SH_REMOVE_HOOK(IServerGameDLL, LevelShutdown, gamedll, SH_MEMBER(this, &SourceModBase::LevelShutdown), false); + SH_REMOVE_HOOK(IServerGameDLL, Think, gamedll, SH_MEMBER(&g_Timers, &TimerSystem::Think), false); SH_REMOVE_HOOK(IServerGameDLL, GameFrame, gamedll, SH_MEMBER(&g_Timers, &TimerSystem::GameFrame), false); - SH_REMOVE_HOOK(IServerGameDLL, Think, gamedll, SH_MEMBER(logicore.callbacks, &IProviderCallbacks::OnThink), false); } void SourceModBase::LogMessage(IExtension *pExt, const char *format, ...)