core/TimerSys.cpp
author Borja Ferrer <faluco@alliedmods.net>
Fri Nov 16 21:34:29 2007 +0000 (2007-11-16)
changeset 1686 185cac1820fe
parent 1662 a5a5bf128cd7
child 1714 f1da232f6ca4
permissions -rw-r--r--
fixed amb1164, SM was crashing due to interface changes from valve (this change is just to make the build update using the latest sdk)
     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 float g_fUniversalTime = 0.0f;
    52 float g_fGameStartTime = 0.0f;	/* Game game start time, non-universal */
    53 float g_fTimerThink = 0.0f;		/* Timer's next think time */
    54 const float *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 float 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 float CalcNextThink(float 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::OnSourceModLevelChange(const char *mapName)
   228 {
   229 	MapChange(true);
   230 }
   231 
   232 void TimerSystem::OnSourceModLevelEnd()
   233 {
   234 	m_bHasMapTickedYet = false;
   235 	m_bHasMapSimulatedYet = false;
   236 }
   237 
   238 void TimerSystem::GameFrame(bool simulating)
   239 {
   240 	if (simulating && m_bHasMapTickedYet)
   241 	{
   242 		g_fUniversalTime += gpGlobals->curtime - m_fLastTickedTime;
   243 		if (!m_bHasMapSimulatedYet)
   244 		{
   245 			m_bHasMapSimulatedYet = true;
   246 			MapTimeLeftChanged();
   247 		}
   248 	}
   249 	else 
   250 	{
   251 		g_fUniversalTime += gpGlobals->interval_per_tick;
   252 	}
   253 
   254 	m_fLastTickedTime = gpGlobals->curtime;
   255 	m_bHasMapTickedYet = true;
   256 
   257 	if (g_fUniversalTime >= g_fTimerThink)
   258 	{
   259 		RunFrame();
   260 
   261 		g_fTimerThink = CalcNextThink(g_fTimerThink, TIMER_MIN_ACCURACY);
   262 	}
   263 
   264 	RunFrameHooks();
   265 
   266 	if (m_pOnGameFrame->GetFunctionCount())
   267 	{
   268 		m_pOnGameFrame->Execute(NULL);
   269 	}
   270 }
   271 
   272 void TimerSystem::RunFrame()
   273 {
   274 	ITimer *pTimer;
   275 	TimerIter iter;
   276 
   277 	float curtime = GetSimulatedTime();
   278 	for (iter=m_SingleTimers.begin(); iter!=m_SingleTimers.end(); )
   279 	{
   280 		pTimer = (*iter);
   281 		if (curtime >= pTimer->m_ToExec)
   282 		{
   283 			pTimer->m_InExec = true;
   284 			pTimer->m_Listener->OnTimer(pTimer, pTimer->m_pData);
   285 			pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData);
   286 			iter = m_SingleTimers.erase(iter);
   287 			m_FreeTimers.push(pTimer);
   288 		} 
   289 		else 
   290 		{
   291 			break;
   292 		}
   293 	}
   294 
   295 	ResultType res;
   296 	for (iter=m_LoopTimers.begin(); iter!=m_LoopTimers.end(); )
   297 	{
   298 		pTimer = (*iter);
   299 		if (curtime >= pTimer->m_ToExec)
   300 		{
   301 			pTimer->m_InExec = true;
   302 			res = pTimer->m_Listener->OnTimer(pTimer, pTimer->m_pData);
   303 			if (pTimer->m_KillMe || (res == Pl_Stop))
   304 			{
   305 				pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData);
   306 				iter = m_LoopTimers.erase(iter);
   307 				m_FreeTimers.push(pTimer);
   308 				continue;
   309 			}
   310 			pTimer->m_InExec = false;
   311 			pTimer->m_ToExec = CalcNextThink(pTimer->m_ToExec, pTimer->m_Interval);
   312 		}
   313 		iter++;
   314 	}
   315 }
   316 
   317 ITimer *TimerSystem::CreateTimer(ITimedEvent *pCallbacks, float fInterval, void *pData, int flags)
   318 {
   319 	ITimer *pTimer;
   320 	TimerIter iter;
   321 	float to_exec = GetSimulatedTime() + fInterval;
   322 
   323 	if (m_FreeTimers.empty())
   324 	{
   325 		pTimer = new ITimer;
   326 	} else {
   327 		pTimer = m_FreeTimers.front();
   328 		m_FreeTimers.pop();
   329 	}
   330 
   331 	pTimer->Initialize(pCallbacks, fInterval, to_exec, pData, flags);
   332 
   333 	if (flags & TIMER_FLAG_REPEAT)
   334 	{
   335 		m_LoopTimers.push_back(pTimer);
   336 		goto return_timer;
   337 	}
   338 
   339 	if (m_SingleTimers.size() >= 1)
   340 	{
   341 		iter = --m_SingleTimers.end();
   342 		if ((*iter)->m_ToExec <= to_exec)
   343 		{
   344 			goto normal_insert_end;
   345 		}
   346 	}
   347 
   348 	for (iter=m_SingleTimers.begin(); iter!=m_SingleTimers.end(); iter++)
   349 	{
   350 		if ((*iter)->m_ToExec >= to_exec)
   351 		{
   352 			m_SingleTimers.insert(iter, pTimer);
   353 			goto return_timer;
   354 		}
   355 	}
   356 
   357 normal_insert_end:
   358 	m_SingleTimers.push_back(pTimer);
   359 
   360 return_timer:
   361 	return pTimer;
   362 }
   363 
   364 void TimerSystem::FireTimerOnce(ITimer *pTimer, bool delayExec)
   365 {
   366 	ResultType res;
   367 
   368 	if (pTimer->m_InExec)
   369 	{
   370 		return;
   371 	}
   372 
   373 	pTimer->m_InExec = true;
   374 	res = pTimer->m_Listener->OnTimer(pTimer, pTimer->m_pData);
   375 
   376 	if (!(pTimer->m_Flags & TIMER_FLAG_REPEAT))
   377 	{
   378 		pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData);
   379 		m_SingleTimers.remove(pTimer);
   380 		m_FreeTimers.push(pTimer);
   381 	} 
   382 	else 
   383 	{
   384 		if ((res != Pl_Stop) && !pTimer->m_KillMe)
   385 		{
   386 			if (delayExec)
   387 			{
   388 				pTimer->m_ToExec = GetSimulatedTime() + pTimer->m_Interval;
   389 			}
   390 			pTimer->m_InExec = false;
   391 			return;
   392 		}
   393 		pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData);
   394 		m_LoopTimers.remove(pTimer);
   395 		m_FreeTimers.push(pTimer);
   396 	}
   397 }
   398 
   399 void TimerSystem::KillTimer(ITimer *pTimer)
   400 {
   401 	if (pTimer->m_KillMe)
   402 	{
   403 		return;
   404 	}
   405 
   406 	if (pTimer->m_InExec)
   407 	{
   408 		pTimer->m_KillMe = true;
   409 		return;
   410 	}
   411 
   412 	pTimer->m_InExec = true; /* The timer it's not really executed but this check needs to be done */
   413 	pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData);
   414 
   415 	if (pTimer->m_Flags & TIMER_FLAG_REPEAT)
   416 	{
   417 		m_LoopTimers.remove(pTimer);
   418 	} else {
   419 		m_SingleTimers.remove(pTimer);
   420 	}
   421 
   422 	m_FreeTimers.push(pTimer);
   423 }
   424 
   425 CStack<ITimer *> s_tokill;
   426 void TimerSystem::MapChange(bool real_mapchange)
   427 {
   428 	ITimer *pTimer;
   429 	TimerIter iter;
   430 
   431 	for (iter=m_SingleTimers.begin(); iter!=m_SingleTimers.end(); iter++)
   432 	{
   433 		pTimer = (*iter);
   434 		if (real_mapchange && (pTimer->m_Flags & TIMER_FLAG_NO_MAPCHANGE))
   435 		{
   436 			s_tokill.push(pTimer);
   437 		}
   438 	}
   439 
   440 	for (iter=m_LoopTimers.begin(); iter!=m_LoopTimers.end(); iter++)
   441 	{
   442 		pTimer = (*iter);
   443 		if (real_mapchange && (pTimer->m_Flags & TIMER_FLAG_NO_MAPCHANGE))
   444 		{
   445 			s_tokill.push(pTimer);
   446 		}
   447 	}
   448 
   449 	while (!s_tokill.empty())
   450 	{
   451 		KillTimer(s_tokill.front());
   452 		s_tokill.pop();
   453 	}
   454 }
   455 
   456 IMapTimer *TimerSystem::SetMapTimer(IMapTimer *pTimer)
   457 {
   458 	IMapTimer *old = m_pMapTimer;
   459 
   460 	m_pMapTimer = pTimer;
   461 
   462 	if (m_pMapTimer)
   463 	{
   464 		m_pMapTimer->SetMapTimerStatus(true);
   465 	}
   466 
   467 	if (old)
   468 	{
   469 		old->SetMapTimerStatus(false);
   470 	}
   471 
   472 	return old;
   473 }
   474 
   475 IMapTimer *TimerSystem::GetMapTimer()
   476 {
   477 	return m_pMapTimer;
   478 }
   479 
   480 void TimerSystem::MapTimeLeftChanged()
   481 {
   482 	m_pOnMapTimeLeftChanged->Execute(NULL);
   483 }
   484 
   485 void TimerSystem::NotifyOfGameStart(float offset)
   486 {
   487 	g_fGameStartTime = gpGlobals->curtime + offset;
   488 }
   489 
   490 float TimerSystem::GetTickedTime()
   491 {
   492 	return g_fUniversalTime;
   493 }
   494 
   495 bool TimerSystem::GetMapTimeLeft(float *time_left)
   496 {
   497 	if (!m_pMapTimer)
   498 	{
   499 		return false;
   500 	}
   501 
   502 	int time_limit;
   503 	if (!m_bHasMapSimulatedYet || (time_limit = m_pMapTimer->GetMapTimeLimit()) < 1)
   504 	{
   505 		*time_left = -1.0f;
   506 	}
   507 	else
   508 	{
   509 		*time_left = (g_fGameStartTime + time_limit * 60.0f) - gpGlobals->curtime;
   510 	}
   511 
   512 	return true;
   513 }
   514