core/TimerSys.cpp
author David Anderson <dvander@alliedmods.net>
Thu Aug 08 22:02:42 2013 -0700 (2013-08-08)
changeset 3980 dc7dfcd855dc
parent 3872 4c93f74cae51
child 4139 de7d1cd00703
permissions -rw-r--r--
More Windows build fix.
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod
     5  * Copyright (C) 2004-2008 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 #include "ConVarManager.h"
    38 #include "logic_bridge.h"
    39 
    40 #define TIMER_MIN_ACCURACY		0.1
    41 
    42 TimerSystem g_Timers;
    43 double g_fUniversalTime = 0.0f;
    44 float g_fGameStartTime = 0.0f;	/* Game game start time, non-universal */
    45 double g_fTimerThink = 0.0f;		/* Timer's next think time */
    46 const double *g_pUniversalTime = &g_fUniversalTime;
    47 ConVar *mp_timelimit = NULL;
    48 int g_TimeLeftMode = 0;
    49 
    50 ConVar sm_time_adjustment("sm_time_adjustment", "0", 0, "Adjusts the server time in seconds");
    51 
    52 inline double GetSimulatedTime()
    53 {
    54 	return g_fUniversalTime;
    55 }
    56 
    57 time_t GetAdjustedTime(time_t *buf)
    58 {
    59 	time_t val = time(NULL) + sm_time_adjustment.GetInt();
    60 	if (buf)
    61 	{
    62 		*buf = val;
    63 	}
    64 	return val;
    65 }
    66 
    67 class DefaultMapTimer : 
    68 	public IMapTimer,
    69 	public SMGlobalClass,
    70 	public IConVarChangeListener
    71 {
    72 public:
    73 	DefaultMapTimer()
    74 	{
    75 		m_bInUse = false;
    76 	}
    77 
    78 	void OnSourceModLevelChange(const char *mapName)
    79 	{
    80 		g_fGameStartTime = 0.0f;
    81 	}
    82 
    83 	int GetMapTimeLimit()
    84 	{
    85 		return mp_timelimit->GetInt();
    86 	}
    87 
    88 	void SetMapTimerStatus(bool enabled)
    89 	{
    90 		if (enabled && !m_bInUse)
    91 		{
    92 			Enable();
    93 		} 
    94 		else if (!enabled && m_bInUse)
    95 		{
    96 			Disable();
    97 		}
    98 		m_bInUse = enabled;
    99 	}
   100 
   101 	void ExtendMapTimeLimit(int extra_time)
   102 	{
   103 		if (extra_time == 0)
   104 		{
   105 			mp_timelimit->SetValue(0);
   106 			return;
   107 		}
   108 
   109 		extra_time /= 60;
   110 
   111 		mp_timelimit->SetValue(mp_timelimit->GetInt() + extra_time);
   112 	}
   113 
   114 	void OnConVarChanged(ConVar *pConVar, const char *oldValue, float flOldValue)
   115 	{
   116 		g_Timers.MapTimeLeftChanged();
   117 	}
   118 
   119 private:
   120 	void Enable()
   121 	{
   122 		g_ConVarManager.AddConVarChangeListener("mp_timelimit", this);
   123 	}
   124 
   125 	void Disable()
   126 	{
   127 		g_ConVarManager.RemoveConVarChangeListener("mp_timelimit", this);
   128 	}
   129 
   130 private:
   131 	bool m_bInUse;
   132 } s_DefaultMapTimer;
   133 
   134 /**
   135  * If the ticking process has run amok (should be impossible), we 
   136  * take care of this by "skipping" the in-between time, to prevent 
   137  * a bazillion times from firing on accident.  This has the result  
   138  * that a drastic jump in time will continue acting normally.  Users 
   139  * may not expect this, but... I think it is the best solution.
   140  */
   141 inline double CalcNextThink(double last, float interval)
   142 {
   143 	if (g_fUniversalTime - last - interval <= TIMER_MIN_ACCURACY)
   144 	{
   145 		return last + interval;
   146 	}
   147 	else
   148 	{
   149 		return g_fUniversalTime + interval;
   150 	}
   151 }
   152 
   153 void ITimer::Initialize(ITimedEvent *pCallbacks, float fInterval, float fToExec, void *pData, int flags)
   154 {
   155 	m_Listener = pCallbacks;
   156 	m_Interval = fInterval;
   157 	m_ToExec = fToExec;
   158 	m_pData = pData;
   159 	m_Flags = flags;
   160 	m_InExec = false;
   161 	m_KillMe = false;
   162 }
   163 
   164 TimerSystem::TimerSystem()
   165 {
   166 	m_pMapTimer = NULL;
   167 	m_bHasMapTickedYet = false;
   168 	m_bHasMapSimulatedYet = false;
   169 	m_fLastTickedTime = 0.0f;
   170 }
   171 
   172 TimerSystem::~TimerSystem()
   173 {
   174 	CStack<ITimer *>::iterator iter;
   175 	for (iter=m_FreeTimers.begin(); iter!=m_FreeTimers.end(); iter++)
   176 	{
   177 		delete (*iter);
   178 	}
   179 	m_FreeTimers.popall();
   180 }
   181 
   182 void TimerSystem::OnSourceModAllInitialized()
   183 {
   184 	sharesys->AddInterface(NULL, this);
   185 	m_pOnGameFrame = g_Forwards.CreateForward("OnGameFrame", ET_Ignore, 0, NULL);
   186 	m_pOnMapTimeLeftChanged = g_Forwards.CreateForward("OnMapTimeLeftChanged", ET_Ignore, 0, NULL);
   187 }
   188 
   189 void TimerSystem::OnSourceModGameInitialized()
   190 {
   191 	mp_timelimit = icvar->FindVar("mp_timelimit");
   192 
   193 	if (m_pMapTimer == NULL && mp_timelimit != NULL)
   194 	{
   195 		SetMapTimer(&s_DefaultMapTimer);
   196 	}
   197 }
   198 
   199 void TimerSystem::OnSourceModShutdown()
   200 {
   201 	SetMapTimer(NULL);
   202 	g_Forwards.ReleaseForward(m_pOnGameFrame);
   203 	g_Forwards.ReleaseForward(m_pOnMapTimeLeftChanged);
   204 }
   205 
   206 void TimerSystem::OnSourceModLevelEnd()
   207 {
   208 	m_bHasMapTickedYet = false;
   209 	m_bHasMapSimulatedYet = false;
   210 }
   211 
   212 void TimerSystem::GameFrame(bool simulating)
   213 {
   214 	if (simulating && m_bHasMapTickedYet)
   215 	{
   216 		g_fUniversalTime += gpGlobals->curtime - m_fLastTickedTime;
   217 		if (!m_bHasMapSimulatedYet)
   218 		{
   219 			m_bHasMapSimulatedYet = true;
   220 			MapTimeLeftChanged();
   221 		}
   222 	}
   223 	else 
   224 	{
   225 		g_fUniversalTime += gpGlobals->interval_per_tick;
   226 	}
   227 
   228 	m_fLastTickedTime = gpGlobals->curtime;
   229 	m_bHasMapTickedYet = true;
   230 
   231 	if (g_fUniversalTime >= g_fTimerThink)
   232 	{
   233 		RunFrame();
   234 
   235 		g_fTimerThink = CalcNextThink(g_fTimerThink, TIMER_MIN_ACCURACY);
   236 	}
   237 
   238 	RunFrameHooks(simulating);
   239 
   240 	if (m_pOnGameFrame->GetFunctionCount())
   241 	{
   242 		m_pOnGameFrame->Execute(NULL);
   243 	}
   244 }
   245 
   246 void TimerSystem::RunFrame()
   247 {
   248 	ITimer *pTimer;
   249 	TimerIter iter;
   250 
   251 	double curtime = GetSimulatedTime();
   252 	for (iter=m_SingleTimers.begin(); iter!=m_SingleTimers.end(); )
   253 	{
   254 		pTimer = (*iter);
   255 		if (curtime >= pTimer->m_ToExec)
   256 		{
   257 			pTimer->m_InExec = true;
   258 			pTimer->m_Listener->OnTimer(pTimer, pTimer->m_pData);
   259 			pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData);
   260 			iter = m_SingleTimers.erase(iter);
   261 			m_FreeTimers.push(pTimer);
   262 		} 
   263 		else 
   264 		{
   265 			break;
   266 		}
   267 	}
   268 
   269 	ResultType res;
   270 	for (iter=m_LoopTimers.begin(); iter!=m_LoopTimers.end(); )
   271 	{
   272 		pTimer = (*iter);
   273 		if (curtime >= pTimer->m_ToExec)
   274 		{
   275 			pTimer->m_InExec = true;
   276 			res = pTimer->m_Listener->OnTimer(pTimer, pTimer->m_pData);
   277 			if (pTimer->m_KillMe || (res == Pl_Stop))
   278 			{
   279 				pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData);
   280 				iter = m_LoopTimers.erase(iter);
   281 				m_FreeTimers.push(pTimer);
   282 				continue;
   283 			}
   284 			pTimer->m_InExec = false;
   285 			pTimer->m_ToExec = CalcNextThink(pTimer->m_ToExec, pTimer->m_Interval);
   286 		}
   287 		iter++;
   288 	}
   289 }
   290 
   291 ITimer *TimerSystem::CreateTimer(ITimedEvent *pCallbacks, float fInterval, void *pData, int flags)
   292 {
   293 	ITimer *pTimer;
   294 	TimerIter iter;
   295 	float to_exec = GetSimulatedTime() + fInterval;
   296 
   297 	if (m_FreeTimers.empty())
   298 	{
   299 		pTimer = new ITimer;
   300 	} else {
   301 		pTimer = m_FreeTimers.front();
   302 		m_FreeTimers.pop();
   303 	}
   304 
   305 	pTimer->Initialize(pCallbacks, fInterval, to_exec, pData, flags);
   306 
   307 	if (flags & TIMER_FLAG_REPEAT)
   308 	{
   309 		m_LoopTimers.push_back(pTimer);
   310 		goto return_timer;
   311 	}
   312 
   313 	if (m_SingleTimers.size() >= 1)
   314 	{
   315 		iter = --m_SingleTimers.end();
   316 		if ((*iter)->m_ToExec <= to_exec)
   317 		{
   318 			goto normal_insert_end;
   319 		}
   320 	}
   321 
   322 	for (iter=m_SingleTimers.begin(); iter!=m_SingleTimers.end(); iter++)
   323 	{
   324 		if ((*iter)->m_ToExec >= to_exec)
   325 		{
   326 			m_SingleTimers.insert(iter, pTimer);
   327 			goto return_timer;
   328 		}
   329 	}
   330 
   331 normal_insert_end:
   332 	m_SingleTimers.push_back(pTimer);
   333 
   334 return_timer:
   335 	return pTimer;
   336 }
   337 
   338 void TimerSystem::FireTimerOnce(ITimer *pTimer, bool delayExec)
   339 {
   340 	ResultType res;
   341 
   342 	if (pTimer->m_InExec)
   343 	{
   344 		return;
   345 	}
   346 
   347 	pTimer->m_InExec = true;
   348 	res = pTimer->m_Listener->OnTimer(pTimer, pTimer->m_pData);
   349 
   350 	if (!(pTimer->m_Flags & TIMER_FLAG_REPEAT))
   351 	{
   352 		pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData);
   353 		m_SingleTimers.remove(pTimer);
   354 		m_FreeTimers.push(pTimer);
   355 	} 
   356 	else 
   357 	{
   358 		if ((res != Pl_Stop) && !pTimer->m_KillMe)
   359 		{
   360 			if (delayExec)
   361 			{
   362 				pTimer->m_ToExec = GetSimulatedTime() + pTimer->m_Interval;
   363 			}
   364 			pTimer->m_InExec = false;
   365 			return;
   366 		}
   367 		pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData);
   368 		m_LoopTimers.remove(pTimer);
   369 		m_FreeTimers.push(pTimer);
   370 	}
   371 }
   372 
   373 void TimerSystem::KillTimer(ITimer *pTimer)
   374 {
   375 	if (pTimer->m_KillMe)
   376 	{
   377 		return;
   378 	}
   379 
   380 	if (pTimer->m_InExec)
   381 	{
   382 		pTimer->m_KillMe = true;
   383 		return;
   384 	}
   385 
   386 	pTimer->m_InExec = true; /* The timer it's not really executed but this check needs to be done */
   387 	pTimer->m_Listener->OnTimerEnd(pTimer, pTimer->m_pData);
   388 
   389 	if (pTimer->m_Flags & TIMER_FLAG_REPEAT)
   390 	{
   391 		m_LoopTimers.remove(pTimer);
   392 	} else {
   393 		m_SingleTimers.remove(pTimer);
   394 	}
   395 
   396 	m_FreeTimers.push(pTimer);
   397 }
   398 
   399 CStack<ITimer *> s_tokill;
   400 void TimerSystem::RemoveMapChangeTimers()
   401 {
   402 	ITimer *pTimer;
   403 	TimerIter iter;
   404 
   405 	for (iter=m_SingleTimers.begin(); iter!=m_SingleTimers.end(); iter++)
   406 	{
   407 		pTimer = (*iter);
   408 		if (pTimer->m_Flags & TIMER_FLAG_NO_MAPCHANGE)
   409 		{
   410 			s_tokill.push(pTimer);
   411 		}
   412 	}
   413 
   414 	for (iter=m_LoopTimers.begin(); iter!=m_LoopTimers.end(); iter++)
   415 	{
   416 		pTimer = (*iter);
   417 		if (pTimer->m_Flags & TIMER_FLAG_NO_MAPCHANGE)
   418 		{
   419 			s_tokill.push(pTimer);
   420 		}
   421 	}
   422 
   423 	while (!s_tokill.empty())
   424 	{
   425 		KillTimer(s_tokill.front());
   426 		s_tokill.pop();
   427 	}
   428 }
   429 
   430 IMapTimer *TimerSystem::SetMapTimer(IMapTimer *pTimer)
   431 {
   432 	IMapTimer *old = m_pMapTimer;
   433 
   434 	m_pMapTimer = pTimer;
   435 
   436 	if (m_pMapTimer)
   437 	{
   438 		m_pMapTimer->SetMapTimerStatus(true);
   439 	}
   440 
   441 	if (old)
   442 	{
   443 		old->SetMapTimerStatus(false);
   444 	}
   445 
   446 	return old;
   447 }
   448 
   449 IMapTimer *TimerSystem::GetMapTimer()
   450 {
   451 	return m_pMapTimer;
   452 }
   453 
   454 void TimerSystem::MapTimeLeftChanged()
   455 {
   456 	m_pOnMapTimeLeftChanged->Execute(NULL);
   457 }
   458 
   459 void TimerSystem::NotifyOfGameStart(float offset)
   460 {
   461 	g_fGameStartTime = gpGlobals->curtime + offset;
   462 }
   463 
   464 float TimerSystem::GetTickedTime()
   465 {
   466 	return g_fUniversalTime;
   467 }
   468 
   469 bool TimerSystem::GetMapTimeLeft(float *time_left)
   470 {
   471 	if (!m_pMapTimer)
   472 	{
   473 		return false;
   474 	}
   475 
   476 	int time_limit;
   477 	if (!m_bHasMapSimulatedYet || (time_limit = m_pMapTimer->GetMapTimeLimit()) < 1)
   478 	{
   479 		*time_left = -1.0f;
   480 	}
   481 	else
   482 	{
   483 		*time_left = (g_fGameStartTime + time_limit * 60.0f) - gpGlobals->curtime;
   484 	}
   485 
   486 	return true;
   487 }
   488