core/TimerSys.cpp
author David Anderson <dvander@alliedmods.net>
Tue Dec 04 15:58:07 2007 +0000 (2007-12-04)
changeset 1764 e4f24e9789fd
parent 1721 d54c379f7171
child 1770 9c545310f947
permissions -rw-r--r--
- removed ancient bcompat shim for translations/core.cfg file
- fixed msvc8 whining about BfReadString
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod
     5  * Copyright (C) 2004-2007 AlliedModders LLC.  All rights reserved.
     6  * =============================================================================
     7  *
     8  * This program is free software; you can redistribute it and/or modify it under
     9  * the terms of the GNU General Public License, version 3.0, as published by the
    10  * Free Software Foundation.
    11  * 
    12  * This program is distributed in the hope that it will be useful, but WITHOUT
    13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    14  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
    15  * details.
    16  *
    17  * You should have received a copy of the GNU General Public License along with
    18  * this program.  If not, see <http://www.gnu.org/licenses/>.
    19  *
    20  * As a special exception, AlliedModders LLC gives you permission to link the
    21  * code of this program (as well as its derivative works) to "Half-Life 2," the
    22  * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
    23  * by the Valve Corporation.  You must obey the GNU General Public License in
    24  * all respects for all other code used.  Additionally, AlliedModders LLC grants
    25  * this exception to all derivative works.  AlliedModders LLC defines further
    26  * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
    27  * or <http://www.sourcemod.net/license.php>.
    28  *
    29  * Version: $Id$
    30  */
    31 
    32 #include <time.h>
    33 #include "TimerSys.h"
    34 #include "ForwardSys.h"
    35 #include "sourcemm_api.h"
    36 #include "frame_hooks.h"
    37 
    38 #define TIMER_MIN_ACCURACY		0.1
    39 
    40 #if !defined ORANGEBOX_BUILD
    41 #define CallGlobalChangeCallbacks	CallGlobalChangeCallback
    42 #endif
    43 
    44 #if defined ORANGEBOX_BUILD
    45 SH_DECL_HOOK3_void(ICvar, CallGlobalChangeCallbacks, SH_NOATTRIB, false, ConVar *, const char *, float);
    46 #else
    47 SH_DECL_HOOK2_void(ICvar, CallGlobalChangeCallbacks, SH_NOATTRIB, false, ConVar *, const char *);
    48 #endif
    49 
    50 TimerSystem g_Timers;
    51 double g_fUniversalTime = 0.0f;
    52 float g_fGameStartTime = 0.0f;	/* Game game start time, non-universal */
    53 double g_fTimerThink = 0.0f;		/* Timer's next think time */
    54 const double *g_pUniversalTime = &g_fUniversalTime;
    55 ConVar *mp_timelimit = NULL;
    56 int g_TimeLeftMode = 0;
    57 
    58 ConVar sm_time_adjustment("sm_time_adjustment", "0", 0, "Adjusts the server time in seconds");
    59 
    60 inline double GetSimulatedTime()
    61 {
    62 	return g_fUniversalTime;
    63 }
    64 
    65 time_t GetAdjustedTime(time_t *buf)
    66 {
    67 	time_t val = time(NULL) + sm_time_adjustment.GetInt();
    68 	if (buf)
    69 	{
    70 		*buf = val;
    71 	}
    72 	return val;
    73 }
    74 
    75 class DefaultMapTimer : 
    76 	public IMapTimer,
    77 	public SMGlobalClass
    78 {
    79 public:
    80 	DefaultMapTimer()
    81 	{
    82 		m_bInUse = false;
    83 	}
    84 
    85 	void OnSourceModLevelChange(const char *mapName)
    86 	{
    87 		g_fGameStartTime = 0.0f;
    88 	}
    89 
    90 	int GetMapTimeLimit()
    91 	{
    92 		return mp_timelimit->GetInt();
    93 	}
    94 
    95 	void SetMapTimerStatus(bool enabled)
    96 	{
    97 		if (enabled && !m_bInUse)
    98 		{
    99 			Enable();
   100 		} 
   101 		else if (!enabled && m_bInUse)
   102 		{
   103 			Disable();
   104 		}
   105 		m_bInUse = enabled;
   106 	}
   107 
   108 	void ExtendMapTimeLimit(int extra_time)
   109 	{
   110 		if (extra_time == 0)
   111 		{
   112 			mp_timelimit->SetValue(0);
   113 			return;
   114 		}
   115 
   116 		extra_time /= 60;
   117 
   118 		mp_timelimit->SetValue(mp_timelimit->GetInt() + extra_time);
   119 	}
   120 
   121 #if defined ORANGEBOX_BUILD
   122 	void GlobalChangeCallback(ConVar *pVar, const char *old_value, float flOldValue)
   123 #else
   124 	void GlobalChangeCallback(ConVar *pVar, const char *old_value)
   125 #endif
   126 	{
   127 		if (pVar != mp_timelimit)
   128 		{
   129 			return;
   130 		}
   131 
   132 		if (atoi(old_value) == pVar->GetInt())
   133 		{
   134 			return;
   135 		}
   136 
   137 		g_Timers.MapTimeLeftChanged();
   138 	}
   139 
   140 private:
   141 	void Enable()
   142 	{
   143 		SH_ADD_HOOK_MEMFUNC(ICvar, CallGlobalChangeCallbacks, icvar, this, &DefaultMapTimer::GlobalChangeCallback, false);
   144 	}
   145 
   146 	void Disable()
   147 	{
   148 		SH_REMOVE_HOOK_MEMFUNC(ICvar, CallGlobalChangeCallbacks, icvar, this, &DefaultMapTimer::GlobalChangeCallback, false);
   149 	}
   150 
   151 private:
   152 	bool m_bInUse;
   153 } s_DefaultMapTimer;
   154 
   155 /**
   156  * If the ticking process has run amok (should be impossible), we 
   157  * take care of this by "skipping" the in-between time, to prevent 
   158  * a bazillion times from firing on accident.  This has the result  
   159  * that a drastic jump in time will continue acting normally.  Users 
   160  * may not expect this, but... I think it is the best solution.
   161  */
   162 inline double CalcNextThink(double last, float interval)
   163 {
   164 	if (g_fUniversalTime - last - interval <= TIMER_MIN_ACCURACY)
   165 	{
   166 		return last + interval;
   167 	}
   168 	else
   169 	{
   170 		return g_fUniversalTime + interval;
   171 	}
   172 }
   173 
   174 void ITimer::Initialize(ITimedEvent *pCallbacks, float fInterval, float fToExec, void *pData, int flags)
   175 {
   176 	m_Listener = pCallbacks;
   177 	m_Interval = fInterval;
   178 	m_ToExec = fToExec;
   179 	m_pData = pData;
   180 	m_Flags = flags;
   181 	m_InExec = false;
   182 	m_KillMe = false;
   183 }
   184 
   185 TimerSystem::TimerSystem()
   186 {
   187 	m_pMapTimer = NULL;
   188 	m_bHasMapTickedYet = false;
   189 	m_bHasMapSimulatedYet = false;
   190 	m_fLastTickedTime = 0.0f;
   191 }
   192 
   193 TimerSystem::~TimerSystem()
   194 {
   195 	CStack<ITimer *>::iterator iter;
   196 	for (iter=m_FreeTimers.begin(); iter!=m_FreeTimers.end(); iter++)
   197 	{
   198 		delete (*iter);
   199 	}
   200 	m_FreeTimers.popall();
   201 }
   202 
   203 void TimerSystem::OnSourceModAllInitialized()
   204 {
   205 	g_ShareSys.AddInterface(NULL, this);
   206 	m_pOnGameFrame = g_Forwards.CreateForward("OnGameFrame", ET_Ignore, 0, NULL);
   207 	m_pOnMapTimeLeftChanged = g_Forwards.CreateForward("OnMapTimeLeftChanged", ET_Ignore, 0, NULL);
   208 }
   209 
   210 void TimerSystem::OnSourceModGameInitialized()
   211 {
   212 	mp_timelimit = icvar->FindVar("mp_timelimit");
   213 
   214 	if (m_pMapTimer == NULL && mp_timelimit != NULL)
   215 	{
   216 		SetMapTimer(&s_DefaultMapTimer);
   217 	}
   218 }
   219 
   220 void TimerSystem::OnSourceModShutdown()
   221 {
   222 	SetMapTimer(NULL);
   223 	g_Forwards.ReleaseForward(m_pOnGameFrame);
   224 	g_Forwards.ReleaseForward(m_pOnMapTimeLeftChanged);
   225 }
   226 
   227 void TimerSystem::OnSourceModLevelEnd()
   228 {
   229 	m_bHasMapTickedYet = false;
   230 	m_bHasMapSimulatedYet = false;
   231 }
   232 
   233 void TimerSystem::GameFrame(bool simulating)
   234 {
   235 	if (simulating && m_bHasMapTickedYet)
   236 	{
   237 		g_fUniversalTime += gpGlobals->curtime - m_fLastTickedTime;
   238 		if (!m_bHasMapSimulatedYet)
   239 		{
   240 			m_bHasMapSimulatedYet = true;
   241 			MapTimeLeftChanged();
   242 		}
   243 	}
   244 	else 
   245 	{
   246 		g_fUniversalTime += gpGlobals->interval_per_tick;
   247 	}
   248 
   249 	m_fLastTickedTime = gpGlobals->curtime;
   250 	m_bHasMapTickedYet = true;
   251 
   252 	if (g_fUniversalTime >= g_fTimerThink)
   253 	{
   254 		RunFrame();
   255 
   256 		g_fTimerThink = CalcNextThink(g_fTimerThink, TIMER_MIN_ACCURACY);
   257 	}
   258 
   259 	RunFrameHooks();
   260 
   261 	if (m_pOnGameFrame->GetFunctionCount())
   262 	{
   263 		m_pOnGameFrame->Execute(NULL);
   264 	}
   265 }
   266 
   267 void TimerSystem::RunFrame()
   268 {
   269 	ITimer *pTimer;
   270 	TimerIter iter;
   271 
   272 	double curtime = GetSimulatedTime();
   273 	for (iter=m_SingleTimers.begin(); iter!=m_SingleTimers.end(); )
   274 	{
   275 		pTimer = (*iter);
   276 		if (curtime >= pTimer->m_ToExec)
   277 		{
   278 			pTimer->m_InExec = true;
   279 			pTimer->m_Listener->OnTimer(pTimer, pTimer->m_pData);
   280 			pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData);
   281 			iter = m_SingleTimers.erase(iter);
   282 			m_FreeTimers.push(pTimer);
   283 		} 
   284 		else 
   285 		{
   286 			break;
   287 		}
   288 	}
   289 
   290 	ResultType res;
   291 	for (iter=m_LoopTimers.begin(); iter!=m_LoopTimers.end(); )
   292 	{
   293 		pTimer = (*iter);
   294 		if (curtime >= pTimer->m_ToExec)
   295 		{
   296 			pTimer->m_InExec = true;
   297 			res = pTimer->m_Listener->OnTimer(pTimer, pTimer->m_pData);
   298 			if (pTimer->m_KillMe || (res == Pl_Stop))
   299 			{
   300 				pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData);
   301 				iter = m_LoopTimers.erase(iter);
   302 				m_FreeTimers.push(pTimer);
   303 				continue;
   304 			}
   305 			pTimer->m_InExec = false;
   306 			pTimer->m_ToExec = CalcNextThink(pTimer->m_ToExec, pTimer->m_Interval);
   307 		}
   308 		iter++;
   309 	}
   310 }
   311 
   312 ITimer *TimerSystem::CreateTimer(ITimedEvent *pCallbacks, float fInterval, void *pData, int flags)
   313 {
   314 	ITimer *pTimer;
   315 	TimerIter iter;
   316 	float to_exec = GetSimulatedTime() + fInterval;
   317 
   318 	if (m_FreeTimers.empty())
   319 	{
   320 		pTimer = new ITimer;
   321 	} else {
   322 		pTimer = m_FreeTimers.front();
   323 		m_FreeTimers.pop();
   324 	}
   325 
   326 	pTimer->Initialize(pCallbacks, fInterval, to_exec, pData, flags);
   327 
   328 	if (flags & TIMER_FLAG_REPEAT)
   329 	{
   330 		m_LoopTimers.push_back(pTimer);
   331 		goto return_timer;
   332 	}
   333 
   334 	if (m_SingleTimers.size() >= 1)
   335 	{
   336 		iter = --m_SingleTimers.end();
   337 		if ((*iter)->m_ToExec <= to_exec)
   338 		{
   339 			goto normal_insert_end;
   340 		}
   341 	}
   342 
   343 	for (iter=m_SingleTimers.begin(); iter!=m_SingleTimers.end(); iter++)
   344 	{
   345 		if ((*iter)->m_ToExec >= to_exec)
   346 		{
   347 			m_SingleTimers.insert(iter, pTimer);
   348 			goto return_timer;
   349 		}
   350 	}
   351 
   352 normal_insert_end:
   353 	m_SingleTimers.push_back(pTimer);
   354 
   355 return_timer:
   356 	return pTimer;
   357 }
   358 
   359 void TimerSystem::FireTimerOnce(ITimer *pTimer, bool delayExec)
   360 {
   361 	ResultType res;
   362 
   363 	if (pTimer->m_InExec)
   364 	{
   365 		return;
   366 	}
   367 
   368 	pTimer->m_InExec = true;
   369 	res = pTimer->m_Listener->OnTimer(pTimer, pTimer->m_pData);
   370 
   371 	if (!(pTimer->m_Flags & TIMER_FLAG_REPEAT))
   372 	{
   373 		pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData);
   374 		m_SingleTimers.remove(pTimer);
   375 		m_FreeTimers.push(pTimer);
   376 	} 
   377 	else 
   378 	{
   379 		if ((res != Pl_Stop) && !pTimer->m_KillMe)
   380 		{
   381 			if (delayExec)
   382 			{
   383 				pTimer->m_ToExec = GetSimulatedTime() + pTimer->m_Interval;
   384 			}
   385 			pTimer->m_InExec = false;
   386 			return;
   387 		}
   388 		pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData);
   389 		m_LoopTimers.remove(pTimer);
   390 		m_FreeTimers.push(pTimer);
   391 	}
   392 }
   393 
   394 void TimerSystem::KillTimer(ITimer *pTimer)
   395 {
   396 	if (pTimer->m_KillMe)
   397 	{
   398 		return;
   399 	}
   400 
   401 	if (pTimer->m_InExec)
   402 	{
   403 		pTimer->m_KillMe = true;
   404 		return;
   405 	}
   406 
   407 	pTimer->m_InExec = true; /* The timer it's not really executed but this check needs to be done */
   408 	pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData);
   409 
   410 	if (pTimer->m_Flags & TIMER_FLAG_REPEAT)
   411 	{
   412 		m_LoopTimers.remove(pTimer);
   413 	} else {
   414 		m_SingleTimers.remove(pTimer);
   415 	}
   416 
   417 	m_FreeTimers.push(pTimer);
   418 }
   419 
   420 CStack<ITimer *> s_tokill;
   421 void TimerSystem::RemoveMapChangeTimers()
   422 {
   423 	ITimer *pTimer;
   424 	TimerIter iter;
   425 
   426 	for (iter=m_SingleTimers.begin(); iter!=m_SingleTimers.end(); iter++)
   427 	{
   428 		pTimer = (*iter);
   429 		if (pTimer->m_Flags & TIMER_FLAG_NO_MAPCHANGE)
   430 		{
   431 			s_tokill.push(pTimer);
   432 		}
   433 	}
   434 
   435 	for (iter=m_LoopTimers.begin(); iter!=m_LoopTimers.end(); iter++)
   436 	{
   437 		pTimer = (*iter);
   438 		if (pTimer->m_Flags & TIMER_FLAG_NO_MAPCHANGE)
   439 		{
   440 			s_tokill.push(pTimer);
   441 		}
   442 	}
   443 
   444 	while (!s_tokill.empty())
   445 	{
   446 		KillTimer(s_tokill.front());
   447 		s_tokill.pop();
   448 	}
   449 }
   450 
   451 IMapTimer *TimerSystem::SetMapTimer(IMapTimer *pTimer)
   452 {
   453 	IMapTimer *old = m_pMapTimer;
   454 
   455 	m_pMapTimer = pTimer;
   456 
   457 	if (m_pMapTimer)
   458 	{
   459 		m_pMapTimer->SetMapTimerStatus(true);
   460 	}
   461 
   462 	if (old)
   463 	{
   464 		old->SetMapTimerStatus(false);
   465 	}
   466 
   467 	return old;
   468 }
   469 
   470 IMapTimer *TimerSystem::GetMapTimer()
   471 {
   472 	return m_pMapTimer;
   473 }
   474 
   475 void TimerSystem::MapTimeLeftChanged()
   476 {
   477 	m_pOnMapTimeLeftChanged->Execute(NULL);
   478 }
   479 
   480 void TimerSystem::NotifyOfGameStart(float offset)
   481 {
   482 	g_fGameStartTime = gpGlobals->curtime + offset;
   483 }
   484 
   485 float TimerSystem::GetTickedTime()
   486 {
   487 	return g_fUniversalTime;
   488 }
   489 
   490 bool TimerSystem::GetMapTimeLeft(float *time_left)
   491 {
   492 	if (!m_pMapTimer)
   493 	{
   494 		return false;
   495 	}
   496 
   497 	int time_limit;
   498 	if (!m_bHasMapSimulatedYet || (time_limit = m_pMapTimer->GetMapTimeLimit()) < 1)
   499 	{
   500 		*time_left = -1.0f;
   501 	}
   502 	else
   503 	{
   504 		*time_left = (g_fGameStartTime + time_limit * 60.0f) - gpGlobals->curtime;
   505 	}
   506 
   507 	return true;
   508 }
   509