diff -r 000000000000 -r c24a6fa957db core/TimerSys.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/TimerSys.cpp Sun Jul 06 04:49:55 2008 +0000 @@ -0,0 +1,487 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#include +#include "TimerSys.h" +#include "ForwardSys.h" +#include "sourcemm_api.h" +#include "frame_hooks.h" +#include "ConVarManager.h" + +#define TIMER_MIN_ACCURACY 0.1 + +TimerSystem g_Timers; +double g_fUniversalTime = 0.0f; +float g_fGameStartTime = 0.0f; /* Game game start time, non-universal */ +double g_fTimerThink = 0.0f; /* Timer's next think time */ +const double *g_pUniversalTime = &g_fUniversalTime; +ConVar *mp_timelimit = NULL; +int g_TimeLeftMode = 0; + +ConVar sm_time_adjustment("sm_time_adjustment", "0", 0, "Adjusts the server time in seconds"); + +inline double GetSimulatedTime() +{ + return g_fUniversalTime; +} + +time_t GetAdjustedTime(time_t *buf) +{ + time_t val = time(NULL) + sm_time_adjustment.GetInt(); + if (buf) + { + *buf = val; + } + return val; +} + +class DefaultMapTimer : + public IMapTimer, + public SMGlobalClass, + public IConVarChangeListener +{ +public: + DefaultMapTimer() + { + m_bInUse = false; + } + + void OnSourceModLevelChange(const char *mapName) + { + g_fGameStartTime = 0.0f; + } + + int GetMapTimeLimit() + { + return mp_timelimit->GetInt(); + } + + void SetMapTimerStatus(bool enabled) + { + if (enabled && !m_bInUse) + { + Enable(); + } + else if (!enabled && m_bInUse) + { + Disable(); + } + m_bInUse = enabled; + } + + void ExtendMapTimeLimit(int extra_time) + { + if (extra_time == 0) + { + mp_timelimit->SetValue(0); + return; + } + + extra_time /= 60; + + mp_timelimit->SetValue(mp_timelimit->GetInt() + extra_time); + } + + void OnConVarChanged(ConVar *pConVar, const char *oldValue, float flOldValue) + { + g_Timers.MapTimeLeftChanged(); + } + +private: + void Enable() + { + g_ConVarManager.AddConVarChangeListener("mp_timelimit", this); + } + + void Disable() + { + g_ConVarManager.RemoveConVarChangeListener("mp_timelimit", this); + } + +private: + bool m_bInUse; +} s_DefaultMapTimer; + +/** + * If the ticking process has run amok (should be impossible), we + * take care of this by "skipping" the in-between time, to prevent + * a bazillion times from firing on accident. This has the result + * that a drastic jump in time will continue acting normally. Users + * may not expect this, but... I think it is the best solution. + */ +inline double CalcNextThink(double last, float interval) +{ + if (g_fUniversalTime - last - interval <= TIMER_MIN_ACCURACY) + { + return last + interval; + } + else + { + return g_fUniversalTime + interval; + } +} + +void ITimer::Initialize(ITimedEvent *pCallbacks, float fInterval, float fToExec, void *pData, int flags) +{ + m_Listener = pCallbacks; + m_Interval = fInterval; + m_ToExec = fToExec; + m_pData = pData; + m_Flags = flags; + m_InExec = false; + m_KillMe = false; +} + +TimerSystem::TimerSystem() +{ + m_pMapTimer = NULL; + m_bHasMapTickedYet = false; + m_bHasMapSimulatedYet = false; + m_fLastTickedTime = 0.0f; +} + +TimerSystem::~TimerSystem() +{ + CStack::iterator iter; + for (iter=m_FreeTimers.begin(); iter!=m_FreeTimers.end(); iter++) + { + delete (*iter); + } + m_FreeTimers.popall(); +} + +void TimerSystem::OnSourceModAllInitialized() +{ + g_ShareSys.AddInterface(NULL, this); + m_pOnGameFrame = g_Forwards.CreateForward("OnGameFrame", ET_Ignore, 0, NULL); + m_pOnMapTimeLeftChanged = g_Forwards.CreateForward("OnMapTimeLeftChanged", ET_Ignore, 0, NULL); +} + +void TimerSystem::OnSourceModGameInitialized() +{ + mp_timelimit = icvar->FindVar("mp_timelimit"); + + if (m_pMapTimer == NULL && mp_timelimit != NULL) + { + SetMapTimer(&s_DefaultMapTimer); + } +} + +void TimerSystem::OnSourceModShutdown() +{ + SetMapTimer(NULL); + g_Forwards.ReleaseForward(m_pOnGameFrame); + g_Forwards.ReleaseForward(m_pOnMapTimeLeftChanged); +} + +void TimerSystem::OnSourceModLevelEnd() +{ + m_bHasMapTickedYet = false; + m_bHasMapSimulatedYet = false; +} + +void TimerSystem::GameFrame(bool simulating) +{ + if (simulating && m_bHasMapTickedYet) + { + g_fUniversalTime += gpGlobals->curtime - m_fLastTickedTime; + if (!m_bHasMapSimulatedYet) + { + m_bHasMapSimulatedYet = true; + MapTimeLeftChanged(); + } + } + else + { + g_fUniversalTime += gpGlobals->interval_per_tick; + } + + m_fLastTickedTime = gpGlobals->curtime; + m_bHasMapTickedYet = true; + + if (g_fUniversalTime >= g_fTimerThink) + { + RunFrame(); + + g_fTimerThink = CalcNextThink(g_fTimerThink, TIMER_MIN_ACCURACY); + } + + RunFrameHooks(simulating); + + if (m_pOnGameFrame->GetFunctionCount()) + { + m_pOnGameFrame->Execute(NULL); + } +} + +void TimerSystem::RunFrame() +{ + ITimer *pTimer; + TimerIter iter; + + double curtime = GetSimulatedTime(); + for (iter=m_SingleTimers.begin(); iter!=m_SingleTimers.end(); ) + { + pTimer = (*iter); + if (curtime >= pTimer->m_ToExec) + { + pTimer->m_InExec = true; + pTimer->m_Listener->OnTimer(pTimer, pTimer->m_pData); + pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData); + iter = m_SingleTimers.erase(iter); + m_FreeTimers.push(pTimer); + } + else + { + break; + } + } + + ResultType res; + for (iter=m_LoopTimers.begin(); iter!=m_LoopTimers.end(); ) + { + pTimer = (*iter); + if (curtime >= pTimer->m_ToExec) + { + pTimer->m_InExec = true; + res = pTimer->m_Listener->OnTimer(pTimer, pTimer->m_pData); + if (pTimer->m_KillMe || (res == Pl_Stop)) + { + pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData); + iter = m_LoopTimers.erase(iter); + m_FreeTimers.push(pTimer); + continue; + } + pTimer->m_InExec = false; + pTimer->m_ToExec = CalcNextThink(pTimer->m_ToExec, pTimer->m_Interval); + } + iter++; + } +} + +ITimer *TimerSystem::CreateTimer(ITimedEvent *pCallbacks, float fInterval, void *pData, int flags) +{ + ITimer *pTimer; + TimerIter iter; + float to_exec = GetSimulatedTime() + fInterval; + + if (m_FreeTimers.empty()) + { + pTimer = new ITimer; + } else { + pTimer = m_FreeTimers.front(); + m_FreeTimers.pop(); + } + + pTimer->Initialize(pCallbacks, fInterval, to_exec, pData, flags); + + if (flags & TIMER_FLAG_REPEAT) + { + m_LoopTimers.push_back(pTimer); + goto return_timer; + } + + if (m_SingleTimers.size() >= 1) + { + iter = --m_SingleTimers.end(); + if ((*iter)->m_ToExec <= to_exec) + { + goto normal_insert_end; + } + } + + for (iter=m_SingleTimers.begin(); iter!=m_SingleTimers.end(); iter++) + { + if ((*iter)->m_ToExec >= to_exec) + { + m_SingleTimers.insert(iter, pTimer); + goto return_timer; + } + } + +normal_insert_end: + m_SingleTimers.push_back(pTimer); + +return_timer: + return pTimer; +} + +void TimerSystem::FireTimerOnce(ITimer *pTimer, bool delayExec) +{ + ResultType res; + + if (pTimer->m_InExec) + { + return; + } + + pTimer->m_InExec = true; + res = pTimer->m_Listener->OnTimer(pTimer, pTimer->m_pData); + + if (!(pTimer->m_Flags & TIMER_FLAG_REPEAT)) + { + pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData); + m_SingleTimers.remove(pTimer); + m_FreeTimers.push(pTimer); + } + else + { + if ((res != Pl_Stop) && !pTimer->m_KillMe) + { + if (delayExec) + { + pTimer->m_ToExec = GetSimulatedTime() + pTimer->m_Interval; + } + pTimer->m_InExec = false; + return; + } + pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData); + m_LoopTimers.remove(pTimer); + m_FreeTimers.push(pTimer); + } +} + +void TimerSystem::KillTimer(ITimer *pTimer) +{ + if (pTimer->m_KillMe) + { + return; + } + + if (pTimer->m_InExec) + { + pTimer->m_KillMe = true; + return; + } + + pTimer->m_InExec = true; /* The timer it's not really executed but this check needs to be done */ + pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData); + + if (pTimer->m_Flags & TIMER_FLAG_REPEAT) + { + m_LoopTimers.remove(pTimer); + } else { + m_SingleTimers.remove(pTimer); + } + + m_FreeTimers.push(pTimer); +} + +CStack s_tokill; +void TimerSystem::RemoveMapChangeTimers() +{ + ITimer *pTimer; + TimerIter iter; + + for (iter=m_SingleTimers.begin(); iter!=m_SingleTimers.end(); iter++) + { + pTimer = (*iter); + if (pTimer->m_Flags & TIMER_FLAG_NO_MAPCHANGE) + { + s_tokill.push(pTimer); + } + } + + for (iter=m_LoopTimers.begin(); iter!=m_LoopTimers.end(); iter++) + { + pTimer = (*iter); + if (pTimer->m_Flags & TIMER_FLAG_NO_MAPCHANGE) + { + s_tokill.push(pTimer); + } + } + + while (!s_tokill.empty()) + { + KillTimer(s_tokill.front()); + s_tokill.pop(); + } +} + +IMapTimer *TimerSystem::SetMapTimer(IMapTimer *pTimer) +{ + IMapTimer *old = m_pMapTimer; + + m_pMapTimer = pTimer; + + if (m_pMapTimer) + { + m_pMapTimer->SetMapTimerStatus(true); + } + + if (old) + { + old->SetMapTimerStatus(false); + } + + return old; +} + +IMapTimer *TimerSystem::GetMapTimer() +{ + return m_pMapTimer; +} + +void TimerSystem::MapTimeLeftChanged() +{ + m_pOnMapTimeLeftChanged->Execute(NULL); +} + +void TimerSystem::NotifyOfGameStart(float offset) +{ + g_fGameStartTime = gpGlobals->curtime + offset; +} + +float TimerSystem::GetTickedTime() +{ + return g_fUniversalTime; +} + +bool TimerSystem::GetMapTimeLeft(float *time_left) +{ + if (!m_pMapTimer) + { + return false; + } + + int time_limit; + if (!m_bHasMapSimulatedYet || (time_limit = m_pMapTimer->GetMapTimeLimit()) < 1) + { + *time_left = -1.0f; + } + else + { + *time_left = (g_fGameStartTime + time_limit * 60.0f) - gpGlobals->curtime; + } + + return true; +} +